diff --git a/.github/buildomat/jobs/deploy.sh b/.github/buildomat/jobs/deploy.sh index 6b867ceaf72..3ce09d88f0b 100755 --- a/.github/buildomat/jobs/deploy.sh +++ b/.github/buildomat/jobs/deploy.sh @@ -2,7 +2,7 @@ #: #: name = "helios / deploy" #: variety = "basic" -#: target = "lab-3.0-opte-0.40" +#: target = "lab-3.0-opte-0.41" #: output_rules = [ #: "%/var/svc/log/oxide-*.log*", #: "%/zone/oxz_*/root/var/svc/log/oxide-*.log*", diff --git a/Cargo.lock b/Cargo.lock index 4fc5e5a215f..123ac656e9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,7 +102,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", - "anstyle-parse", + "anstyle-parse 0.2.7", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse 1.0.0", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -112,9 +127,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -125,6 +140,15 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + [[package]] name = "anstyle-query" version = "1.1.5" @@ -1041,7 +1065,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8c236e531d024b1524669ee2a56eca09ab8a40f3395dc2728cde9defa9c60d8" dependencies = [ - "anstream", + "anstream 0.6.21", "anstyle", "camino", "camino-tempfile", @@ -1210,7 +1234,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -1379,9 +1403,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -1389,11 +1413,11 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "clap_lex", "strsim 0.11.1", @@ -1402,9 +1426,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1580,6 +1604,11 @@ dependencies = [ "tokio", ] +[[package]] +name = "client-common" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/maghemite?rev=db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7#db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" + [[package]] name = "clipboard-win" version = "5.4.1" @@ -1697,7 +1726,7 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/dendrite?branch=main#f20f786e67c86388dfaaf0ef3aa9d2693dfe1da4" +source = "git+https://github.com/oxidecomputer/dendrite?branch=main#cc0c307c617f2988aafdca4e3bd35ea178b64801" dependencies = [ "anyhow", "chrono", @@ -2537,16 +2566,40 @@ dependencies = [ [[package]] name = "ddm-admin-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/maghemite?rev=7696ee48d5ee29a917dea459e281fe2e8ff20513#7696ee48d5ee29a917dea459e281fe2e8ff20513" +source = "git+https://github.com/oxidecomputer/maghemite?rev=db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7#db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" dependencies = [ + "ddm-api-types-versions", "oxnet", - "progenitor 0.13.0", + "progenitor 0.14.0", "reqwest 0.13.2", "serde", "slog", "uuid", ] +[[package]] +name = "ddm-api-types-versions" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/maghemite?rev=db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7#db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" +dependencies = [ + "ddm-protocol", + "oxnet", + "schemars 0.8.22", + "serde", + "serde_repr", + "uuid", +] + +[[package]] +name = "ddm-protocol" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/maghemite?rev=db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7#db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" +dependencies = [ + "oxnet", + "schemars 0.8.22", + "serde", +] + [[package]] name = "debug-ignore" version = "1.0.5" @@ -2953,7 +3006,7 @@ source = "git+https://github.com/oxidecomputer/dlpi-sys#d9645f8d61187e76384474b1 dependencies = [ "libc", "libdlpi-sys", - "num_enum 0.7.5", + "num_enum 0.7.6", "pretty-hex", "thiserror 2.0.18", ] @@ -3096,7 +3149,7 @@ checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" [[package]] name = "dpd-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/dendrite?branch=main#f20f786e67c86388dfaaf0ef3aa9d2693dfe1da4" +source = "git+https://github.com/oxidecomputer/dendrite?branch=main#cc0c307c617f2988aafdca4e3bd35ea178b64801" dependencies = [ "async-trait", "chrono", @@ -3525,7 +3578,7 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ - "anstream", + "anstream 0.6.21", "anstyle", "env_filter", "log", @@ -5277,7 +5330,7 @@ checksum = "a4b8219c9c8c2c844dfd5772ec0bda5cd2a81d78c4579aba97f699721d46ab24" [[package]] name = "illumos-sys-hdrs" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=2c6efefe14321dafe7e9e80129d38316adb2d238#2c6efefe14321dafe7e9e80129d38316adb2d238" +source = "git+https://github.com/oxidecomputer/opte?rev=8b44982bf0d3f8ea52c810b2b00425a1e350e2f8#8b44982bf0d3f8ea52c810b2b00425a1e350e2f8" dependencies = [ "bitflags 2.11.0", ] @@ -5960,7 +6013,7 @@ dependencies = [ [[package]] name = "kstat-macro" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=2c6efefe14321dafe7e9e80129d38316adb2d238#2c6efefe14321dafe7e9e80129d38316adb2d238" +source = "git+https://github.com/oxidecomputer/opte?rev=8b44982bf0d3f8ea52c810b2b00425a1e350e2f8#8b44982bf0d3f8ea52c810b2b00425a1e350e2f8" dependencies = [ "quote", "syn 2.0.117", @@ -6121,7 +6174,7 @@ dependencies = [ "colored 3.1.1", "dlpi", "libc", - "num_enum 0.7.5", + "num_enum 0.7.6", "nvpair", "nvpair-sys", "oxnet", @@ -6198,7 +6251,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33" dependencies = [ - "anstream", + "anstream 0.6.21", "anstyle", "clap", "escape8259", @@ -6295,14 +6348,15 @@ dependencies = [ [[package]] name = "lldpd-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/lldp#d22509dfdb051321b859e924948605115691b93c" +source = "git+https://github.com/oxidecomputer/lldp#54b266174d4de9628bca9c97b0db176e16f12154" dependencies = [ "chrono", "futures", "lldpd-common", - "progenitor 0.11.2", + "lldpd-types-versions", + "progenitor 0.13.0", "protocol", - "reqwest 0.12.28", + "reqwest 0.13.2", "schemars 0.8.22", "serde", "serde_json", @@ -6313,7 +6367,7 @@ dependencies = [ [[package]] name = "lldpd-common" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/lldp#d22509dfdb051321b859e924948605115691b93c" +source = "git+https://github.com/oxidecomputer/lldp#54b266174d4de9628bca9c97b0db176e16f12154" dependencies = [ "anyhow", "dpd-client 0.1.0 (git+https://github.com/oxidecomputer/dendrite?branch=main)", @@ -6327,6 +6381,18 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "lldpd-types-versions" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/lldp#54b266174d4de9628bca9c97b0db176e16f12154" +dependencies = [ + "chrono", + "protocol", + "schemars 0.8.22", + "serde", + "uuid", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -6497,12 +6563,13 @@ dependencies = [ [[package]] name = "mg-admin-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/maghemite?rev=7696ee48d5ee29a917dea459e281fe2e8ff20513#7696ee48d5ee29a917dea459e281fe2e8ff20513" +source = "git+https://github.com/oxidecomputer/maghemite?rev=db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7#db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" dependencies = [ "chrono", + "client-common", "colored 3.1.1", - "progenitor 0.13.0", - "rdb-types", + "mg-api-types-versions", + "progenitor 0.14.0", "reqwest 0.13.2", "schemars 0.8.22", "serde", @@ -6512,6 +6579,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "mg-api-types" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/maghemite?rev=db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7#db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" +dependencies = [ + "mg-api-types-versions", +] + +[[package]] +name = "mg-api-types-versions" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/maghemite?rev=db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7#db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" +dependencies = [ + "chrono", + "nom 8.0.0", + "num_enum 0.7.6", + "oxnet", + "schemars 0.8.22", + "serde", + "slog", + "thiserror 2.0.18", + "uuid", +] + [[package]] name = "mgs-dev" version = "0.1.0" @@ -6989,6 +7080,7 @@ dependencies = [ "nexus-reconfigurator-planning", "nexus-test-utils", "nexus-types", + "nexus-types-versions", "nonempty", "omicron-cockroach-metrics", "omicron-common", @@ -7678,6 +7770,7 @@ dependencies = [ "nexus-lockstep-client", "nexus-test-interface", "nexus-types", + "nexus-types-versions", "omicron-cockroach-admin", "omicron-common", "omicron-passwords", @@ -7803,6 +7896,7 @@ dependencies = [ "dropshot", "http", "mg-admin-client", + "mg-api-types", "omicron-common", "omicron-passwords", "omicron-uuid-kinds", @@ -7889,6 +7983,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "nonempty" version = "0.12.0" @@ -8099,11 +8202,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ - "num_enum_derive 0.7.5", + "num_enum_derive 0.7.6", "rustversion", ] @@ -8121,9 +8224,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", @@ -8670,6 +8773,7 @@ dependencies = [ "macaddr", "maplit", "mg-admin-client", + "mg-api-types", "nexus-auth", "nexus-background-task-interface", "nexus-client", @@ -8736,7 +8840,6 @@ dependencies = [ "range-requests", "raw-cpuid", "rcgen", - "rdb-types", "ref-cast", "regex", "reqwest 0.13.2", @@ -9311,6 +9414,7 @@ version = "0.1.0" dependencies = [ "ahash", "aho-corasick", + "anstream 0.6.21", "anyhow", "aws-lc-rs", "base16ct", @@ -9610,7 +9714,7 @@ dependencies = [ [[package]] name = "opte" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=2c6efefe14321dafe7e9e80129d38316adb2d238#2c6efefe14321dafe7e9e80129d38316adb2d238" +source = "git+https://github.com/oxidecomputer/opte?rev=8b44982bf0d3f8ea52c810b2b00425a1e350e2f8#8b44982bf0d3f8ea52c810b2b00425a1e350e2f8" dependencies = [ "bitflags 2.11.0", "dyn-clone", @@ -9629,7 +9733,7 @@ dependencies = [ [[package]] name = "opte-api" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=2c6efefe14321dafe7e9e80129d38316adb2d238#2c6efefe14321dafe7e9e80129d38316adb2d238" +source = "git+https://github.com/oxidecomputer/opte?rev=8b44982bf0d3f8ea52c810b2b00425a1e350e2f8#8b44982bf0d3f8ea52c810b2b00425a1e350e2f8" dependencies = [ "illumos-sys-hdrs", "ingot", @@ -9642,7 +9746,7 @@ dependencies = [ [[package]] name = "opte-ioctl" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=2c6efefe14321dafe7e9e80129d38316adb2d238#2c6efefe14321dafe7e9e80129d38316adb2d238" +source = "git+https://github.com/oxidecomputer/opte?rev=8b44982bf0d3f8ea52c810b2b00425a1e350e2f8#8b44982bf0d3f8ea52c810b2b00425a1e350e2f8" dependencies = [ "libc", "libnet", @@ -9739,7 +9843,7 @@ dependencies = [ [[package]] name = "oxide-vpc" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=2c6efefe14321dafe7e9e80129d38316adb2d238#2c6efefe14321dafe7e9e80129d38316adb2d238" +source = "git+https://github.com/oxidecomputer/opte?rev=8b44982bf0d3f8ea52c810b2b00425a1e350e2f8#8b44982bf0d3f8ea52c810b2b00425a1e350e2f8" dependencies = [ "cfg-if", "illumos-sys-hdrs", @@ -9896,7 +10000,7 @@ dependencies = [ "indexmap 2.14.0", "itertools 0.14.0", "libc", - "nom", + "nom 7.1.3", "num", "omicron-common", "omicron-test-utils", @@ -10196,9 +10300,9 @@ dependencies = [ [[package]] name = "oxnet" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc6fb07ecd6d2a17ff1431bc5b3ce11036c0b6dd93a3c4904db5b910817b162" +checksum = "057865b45bb202b17ed475d8f22f0416412de2c317c168fefecf9d207faf048d" dependencies = [ "ipnetwork", "schemars 0.8.22", @@ -10611,7 +10715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cd31dcfdbbd7431a807ef4df6edd6473228e94d5c805e8cf671227a21bad068" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "rand 0.8.6", @@ -11439,7 +11543,7 @@ dependencies = [ [[package]] name = "protocol" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/lldp#d22509dfdb051321b859e924948605115691b93c" +source = "git+https://github.com/oxidecomputer/lldp#54b266174d4de9628bca9c97b0db176e16f12154" dependencies = [ "anyhow", "schemars 0.8.22", @@ -11822,16 +11926,6 @@ dependencies = [ "yasna", ] -[[package]] -name = "rdb-types" -version = "0.1.0" -source = "git+https://github.com/oxidecomputer/maghemite?rev=7696ee48d5ee29a917dea459e281fe2e8ff20513#7696ee48d5ee29a917dea459e281fe2e8ff20513" -dependencies = [ - "oxnet", - "schemars 0.8.22", - "serde", -] - [[package]] name = "reconfigurator-cli" version = "0.1.0" @@ -13572,11 +13666,11 @@ dependencies = [ "internal-dns-resolver", "internal-dns-types", "mg-admin-client", + "mg-api-types", "omicron-common", "omicron-ddm-admin-client", "omicron-workspace-hack", "oxnet", - "rdb-types", "sled-agent-types", "slog", "slog-error-chain", @@ -14130,7 +14224,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.117", @@ -15601,7 +15695,7 @@ dependencies = [ [[package]] name = "transceiver-controller" version = "0.1.1" -source = "git+https://github.com/oxidecomputer/transceiver-control?branch=main#11afc484d5957b13d3058e44db274aa720cea1c4" +source = "git+https://github.com/oxidecomputer/transceiver-control?branch=main#81167659157860d6587713b3362b7bc791dfb530" dependencies = [ "anyhow", "clap", @@ -15649,7 +15743,7 @@ dependencies = [ [[package]] name = "transceiver-decode" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/transceiver-control?branch=main#11afc484d5957b13d3058e44db274aa720cea1c4" +source = "git+https://github.com/oxidecomputer/transceiver-control?branch=main#81167659157860d6587713b3362b7bc791dfb530" dependencies = [ "schemars 0.8.22", "serde", @@ -15673,7 +15767,7 @@ dependencies = [ [[package]] name = "transceiver-messages" version = "0.1.1" -source = "git+https://github.com/oxidecomputer/transceiver-control?branch=main#11afc484d5957b13d3058e44db274aa720cea1c4" +source = "git+https://github.com/oxidecomputer/transceiver-control?branch=main#81167659157860d6587713b3362b7bc791dfb530" dependencies = [ "bitflags 2.11.0", "clap", diff --git a/Cargo.toml b/Cargo.toml index 7b2a8470a87..7f674295eda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -615,8 +615,8 @@ ntp-admin-client = { path = "clients/ntp-admin-client" } ntp-admin-v1-client = { path = "clients/ntp-admin-v1-client" } ntp-admin-types = { path = "ntp-admin/types" } ntp-admin-types-versions = { path = "ntp-admin/types/versions" } -mg-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "7696ee48d5ee29a917dea459e281fe2e8ff20513" } -ddm-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "7696ee48d5ee29a917dea459e281fe2e8ff20513" } +mg-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" } +ddm-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" } multimap = "0.10.1" nexus-auth = { path = "nexus/auth" } nexus-background-task-interface = { path = "nexus/background-task-interface" } @@ -678,16 +678,16 @@ omicron-workspace-hack = "0.1.0" omicron-zone-package = "0.12.2" oxide-client = { path = "clients/oxide-client" } oxide-tokio-rt = "0.1.4" -oxide-vpc = { git = "https://github.com/oxidecomputer/opte", rev = "2c6efefe14321dafe7e9e80129d38316adb2d238", features = [ "api", "std" ] } +oxide-vpc = { git = "https://github.com/oxidecomputer/opte", rev = "8b44982bf0d3f8ea52c810b2b00425a1e350e2f8", features = [ "api", "std" ] } oxlog = { path = "dev-tools/oxlog" } -oxnet = "0.1.4" +oxnet = "0.1.5" once_cell = "1.21.3" openapi-lint = { git = "https://github.com/oxidecomputer/openapi-lint", branch = "main" } openapiv3 = "2.2.0" # must match samael's crate! openssl = "0.10" openssl-sys = "0.9" -opte-ioctl = { git = "https://github.com/oxidecomputer/opte", rev = "2c6efefe14321dafe7e9e80129d38316adb2d238" } +opte-ioctl = { git = "https://github.com/oxidecomputer/opte", rev = "8b44982bf0d3f8ea52c810b2b00425a1e350e2f8" } oso = "0.27" owo-colors = "4.2.2" oximeter = { path = "oximeter/oximeter" } @@ -753,7 +753,7 @@ rats-corim = { git = "https://github.com/oxidecomputer/rats-corim.git", rev = "f raw-cpuid = { git = "https://github.com/oxidecomputer/rust-cpuid.git", rev = "a4cf01df76f35430ff5d39dc2fe470bcb953503b" } rayon = "1.10" rcgen = "0.12.1" -rdb-types = { git = "https://github.com/oxidecomputer/maghemite", rev = "7696ee48d5ee29a917dea459e281fe2e8ff20513" } +mg-api-types = { git = "https://github.com/oxidecomputer/maghemite", rev = "db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" } reconfigurator-cli = { path = "dev-tools/reconfigurator-cli" } reedline = "0.40.0" ref-cast = "1.0" diff --git a/common/src/address.rs b/common/src/address.rs index af34bcff8f7..480777dcbac 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -24,6 +24,11 @@ pub const AZ_PREFIX: u8 = 48; pub const RACK_PREFIX: u8 = 56; pub const SLED_PREFIX: u8 = 64; +/// Effective MTU for external-facing OPTE ports when jumbo frames have been +/// opted into. 500 bytes of headroom under the 9000 byte underlay MTU leaves +/// room for encapsulation overhead. +pub const EXTERNAL_JUMBO_FRAMES_MTU: u32 = 8500; + // Multicast constants /// IPv4 Source-Specific Multicast (SSM) subnet. diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 8831d8c630b..7c4d904ee31 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -1176,37 +1176,6 @@ pub struct InstanceRuntimeState { pub time_last_auto_restarted: Option>, } -/// View of an Instance -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Instance { - // TODO is flattening here the intent in RFD 4? - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// ID for the project containing this instance - pub project_id: Uuid, - - /// Number of CPUs allocated for this instance - pub ncpus: InstanceCpuCount, - /// Memory allocated for this instance - pub memory: ByteCount, - /// RFC1035-compliant hostname for the instance - pub hostname: String, - - /// The ID of the disk used to boot this instance, if a specific one is assigned - pub boot_disk_id: Option, - - #[serde(flatten)] - pub runtime: InstanceRuntimeState, - - #[serde(flatten)] - pub auto_restart_status: InstanceAutoRestartStatus, - - /// The CPU platform for this instance. If this is `null`, the instance - /// requires no particular CPU platform. - pub cpu_platform: Option, -} - /// Status of control-plane driven automatic failure recovery for this instance. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct InstanceAutoRestartStatus { diff --git a/end-to-end-tests/src/instance_launch.rs b/end-to-end-tests/src/instance_launch.rs index 308adb09e29..778e29af9e7 100644 --- a/end-to-end-tests/src/instance_launch.rs +++ b/end-to-end-tests/src/instance_launch.rs @@ -88,6 +88,7 @@ async fn instance_launch() -> Result<()> { anti_affinity_groups: Vec::new(), cpu_platform: None, multicast_groups: Vec::new(), + enable_jumbo_frames: false, }) .send() .await?; diff --git a/illumos-utils/src/opte/non_illumos.rs b/illumos-utils/src/opte/non_illumos.rs index 42487cde09c..8f0c4d3175a 100644 --- a/illumos-utils/src/opte/non_illumos.rs +++ b/illumos-utils/src/opte/non_illumos.rs @@ -209,7 +209,7 @@ impl Handle { &self, name: &str, cfg: VpcCfg, - _: bool, + _mtu: Option, ) -> Result { let name = name.to_string(); let IpCfg::Ipv4(ip_cfg) = cfg.ip_cfg else { diff --git a/illumos-utils/src/opte/port_manager.rs b/illumos-utils/src/opte/port_manager.rs index 638dd52de3d..f2b1b134c1d 100644 --- a/illumos-utils/src/opte/port_manager.rs +++ b/illumos-utils/src/opte/port_manager.rs @@ -147,6 +147,9 @@ pub struct PortCreateParams<'a> { pub firewall_rules: &'a [ResolvedVpcFirewallRule], pub dhcp_config: DhcpCfg, pub attached_subnets: Vec, + /// MTU to set on the xde device, in bytes. If `None`, OPTE applies its + /// default (1500). Used by jumbo-frame opt-in. + pub mtu: Option, } impl<'a> TryFrom<&PortCreateParams<'a>> for IpCfg { @@ -371,6 +374,7 @@ impl PortManager { firewall_rules, dhcp_config, attached_subnets: _, + mtu, } = params; let is_service = matches!(nic.kind, NetworkInterfaceKind::Service { .. }); @@ -410,7 +414,7 @@ impl PortManager { ); let hdl = { let hdl = Handle::new()?; - hdl.create_xde(&port_name, vpc_cfg, /* passthru = */ false)?; + hdl.create_xde(&port_name, vpc_cfg, mtu)?; hdl }; let (port, ticket) = { @@ -1335,6 +1339,7 @@ mod tests { dns6_servers: Vec::new(), }, attached_subnets: vec![], + mtu: None, }) .unwrap(); @@ -1514,6 +1519,7 @@ mod tests { dns6_servers: Vec::new(), }, attached_subnets: vec![], + mtu: None, }) .unwrap(); @@ -1685,6 +1691,7 @@ mod tests { dns6_servers: vec![], }, attached_subnets: vec![], + mtu: None, }; let IpCfg::Ipv4(oxide_vpc::api::Ipv4Cfg { vpc_subnet, @@ -1758,6 +1765,7 @@ mod tests { dns6_servers: vec![], }, attached_subnets: vec![], + mtu: None, }; let IpCfg::Ipv6(oxide_vpc::api::Ipv6Cfg { vpc_subnet, @@ -1842,6 +1850,7 @@ mod tests { dns6_servers: vec![], }, attached_subnets: vec![], + mtu: None, }; let IpCfg::DualStack { ipv4, ipv6 } = IpCfg::try_from(&prs).unwrap() else { @@ -1932,6 +1941,7 @@ mod tests { dns6_servers: vec![], }, attached_subnets: vec![], + mtu: None, }; let _ = IpCfg::try_from(&prs).expect_err( "Should fail to convert with public IPv6 and private IPv4", @@ -1978,6 +1988,7 @@ mod tests { dns6_servers: vec![], }, attached_subnets: vec![], + mtu: None, }; let _ = IpCfg::try_from(&prs).expect_err( "Should fail to convert with public IPv4 and private IPv6", diff --git a/nexus/Cargo.toml b/nexus/Cargo.toml index cdb45d1db77..cf894eb6b76 100644 --- a/nexus/Cargo.toml +++ b/nexus/Cargo.toml @@ -96,7 +96,7 @@ qorb.workspace = true rand.workspace = true range-requests.workspace = true ref-cast.workspace = true -rdb-types.workspace = true +mg-api-types.workspace = true regex.workspace = true reqwest = { workspace = true, features = ["http2", "json"] } ring.workspace = true diff --git a/nexus/db-model/src/instance.rs b/nexus/db-model/src/instance.rs index 8e91ffed8da..ede9d6355e1 100644 --- a/nexus/db-model/src/instance.rs +++ b/nexus/db-model/src/instance.rs @@ -136,6 +136,13 @@ pub struct Instance { /// a sled by any other constraints the instance will be incarnated with the /// most general CPU platform supported by the selected sled. pub cpu_platform: Option, + + /// When true, the instance has opted in to jumbo frames (8500 byte MTU) + /// on its primary OPTE interface. The effective MTU also depends on the + /// fleet-wide setting in `system_networking_settings`; if that flag is off + /// the OPTE port is created with the default MTU regardless of this field. + /// Changes to this field only take effect on the next instance restart. + pub enable_jumbo_frames: bool, } impl Instance { @@ -185,6 +192,7 @@ impl Instance { boot_disk_id: None, intended_state, cpu_platform: params.cpu_platform.map(Into::into), + enable_jumbo_frames: params.enable_jumbo_frames, } } diff --git a/nexus/db-model/src/lib.rs b/nexus/db-model/src/lib.rs index bd78fd55f6d..b6c1cd8688c 100644 --- a/nexus/db-model/src/lib.rs +++ b/nexus/db-model/src/lib.rs @@ -195,6 +195,7 @@ mod serde_time_delta; mod silo_auth_settings; mod switch_interface; mod switch_port; +mod system_networking_settings; mod target_release; mod trust_quorum; mod v2p_mapping; @@ -373,6 +374,7 @@ pub use support_bundle::*; pub use switch::*; pub use switch_interface::*; pub use switch_port::*; +pub use system_networking_settings::*; pub use target_release::*; pub use trust_quorum::*; pub use tuf_repo::*; diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index 5caf71e9e28..d03570f70d8 100644 --- a/nexus/db-model/src/schema_versions.rs +++ b/nexus/db-model/src/schema_versions.rs @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock}; /// /// This must be updated when you change the database schema. Refer to /// schema/crdb/README.adoc in the root of this repository for details. -pub const SCHEMA_VERSION: Version = Version::new(261, 0, 0); +pub const SCHEMA_VERSION: Version = Version::new(262, 0, 0); /// List of all past database schema versions, in *reverse* order /// @@ -28,6 +28,7 @@ pub static KNOWN_VERSIONS: LazyLock> = LazyLock::new(|| { // | leaving the first copy as an example for the next person. // v // KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"), + KnownVersion::new(262, "external-jumbo-frames"), KnownVersion::new(261, "remove-add-zones-with-mupdate-override"), KnownVersion::new(260, "ereport-trim-serial-trailing-nulls"), KnownVersion::new(259, "vmm-failure-reason"), diff --git a/nexus/db-model/src/system_networking_settings.rs b/nexus/db-model/src/system_networking_settings.rs new file mode 100644 index 00000000000..d3212ac109e --- /dev/null +++ b/nexus/db-model/src/system_networking_settings.rs @@ -0,0 +1,37 @@ +// 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 chrono::{DateTime, Utc}; +use nexus_db_schema::schema::system_networking_settings; +use serde::{Deserialize, Serialize}; + +/// Singleton row holding fleet-wide networking settings. +#[derive( + Queryable, + Insertable, + Debug, + Clone, + Selectable, + Serialize, + Deserialize, + AsChangeset, +)] +#[diesel(table_name = system_networking_settings)] +pub struct SystemNetworkingSettings { + pub singleton: bool, + pub time_created: DateTime, + pub time_modified: DateTime, + /// When true, end users may opt in to jumbo frames on the primary + /// interface of an instance. When false, the per-instance opt-in is + /// ignored and OPTE ports are created with the default MTU. + pub external_jumbo_frames_opt_in_enabled: bool, +} + +/// Updates to the [`SystemNetworkingSettings`] singleton. +#[derive(AsChangeset)] +#[diesel(table_name = system_networking_settings)] +pub struct SystemNetworkingSettingsUpdate { + pub external_jumbo_frames_opt_in_enabled: Option, + pub time_modified: DateTime, +} diff --git a/nexus/db-queries/Cargo.toml b/nexus/db-queries/Cargo.toml index 476c2cec224..9f016e857c4 100644 --- a/nexus/db-queries/Cargo.toml +++ b/nexus/db-queries/Cargo.toml @@ -71,6 +71,7 @@ nexus-db-model.workspace = true nexus-db-lookup.workspace = true nexus-db-schema.workspace = true nexus-types.workspace = true +nexus-types-versions.workspace = true sled-agent-types.workspace = true sled-hardware-types.workspace = true trust-quorum-types.workspace = true diff --git a/nexus/db-queries/src/db/datastore/instance.rs b/nexus/db-queries/src/db/datastore/instance.rs index b6d199da917..52a792c7877 100644 --- a/nexus/db-queries/src/db/datastore/instance.rs +++ b/nexus/db-queries/src/db/datastore/instance.rs @@ -48,6 +48,7 @@ use nexus_db_lookup::DbConnection; use nexus_db_lookup::LookupPath; use nexus_db_model::Disk; use nexus_types::internal_api::background::ReincarnationReason; +use nexus_types_versions::latest; use omicron_common::api; use omicron_common::api::external; use omicron_common::api::external::CreateResult; @@ -208,7 +209,7 @@ impl From<(Instance, Option)> for InstanceAndActiveVmm { } } -impl From for external::Instance { +impl From for latest::instance::Instance { fn from(value: InstanceAndActiveVmm) -> Self { let time_run_state_updated = value .vmm @@ -274,8 +275,8 @@ impl From for external::Instance { .instance .time_last_auto_restarted, }, - auto_restart_status, + enable_jumbo_frames: value.instance.enable_jumbo_frames, } } } @@ -1203,6 +1204,26 @@ impl DataStore { Ok(instance_and_vmm) } + /// Update the per-instance jumbo-frames opt-in. Changes take effect on the + /// next instance restart. + pub async fn instance_set_enable_jumbo_frames( + &self, + opctx: &OpContext, + authz_instance: &authz::Instance, + enable_jumbo_frames: bool, + ) -> Result<(), Error> { + opctx.authorize(authz::Action::Modify, authz_instance).await?; + use nexus_db_schema::schema::instance::dsl as instance_dsl; + diesel::update(instance_dsl::instance) + .filter(instance_dsl::id.eq(authz_instance.id())) + .filter(instance_dsl::time_deleted.is_null()) + .set(instance_dsl::enable_jumbo_frames.eq(enable_jumbo_frames)) + .execute_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + Ok(()) + } + /// Set the boot disk on an instance, bypassing the rest of an instance /// update. You probably don't need this; it's only used at the end of /// instance creation, since the boot disk can't be set until the new @@ -2376,6 +2397,7 @@ mod tests { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ), ) diff --git a/nexus/db-queries/src/db/datastore/migration.rs b/nexus/db-queries/src/db/datastore/migration.rs index e36789d3ed3..b05a2afd272 100644 --- a/nexus/db-queries/src/db/datastore/migration.rs +++ b/nexus/db-queries/src/db/datastore/migration.rs @@ -242,6 +242,7 @@ mod tests { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ), ) diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index 80d2e480584..a691c6836a3 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -137,6 +137,7 @@ mod support_bundle; mod switch; mod switch_interface; mod switch_port; +mod system_networking_settings; mod target_release; #[cfg(test)] pub(crate) mod test_utils; diff --git a/nexus/db-queries/src/db/datastore/sled.rs b/nexus/db-queries/src/db/datastore/sled.rs index 36872c074c6..13f7f3e88ae 100644 --- a/nexus/db-queries/src/db/datastore/sled.rs +++ b/nexus/db-queries/src/db/datastore/sled.rs @@ -4423,6 +4423,7 @@ pub(in crate::db::datastore) mod test { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ), ) diff --git a/nexus/db-queries/src/db/datastore/system_networking_settings.rs b/nexus/db-queries/src/db/datastore/system_networking_settings.rs new file mode 100644 index 00000000000..23ec8e4211e --- /dev/null +++ b/nexus/db-queries/src/db/datastore/system_networking_settings.rs @@ -0,0 +1,59 @@ +// 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/. + +//! Datastore access for fleet-wide networking settings. + +use super::DataStore; +use crate::authz; +use crate::context::OpContext; +use async_bb8_diesel::AsyncRunQueryDsl; +use chrono::Utc; +use diesel::prelude::*; +use nexus_db_errors::ErrorHandler; +use nexus_db_errors::public_error_from_diesel; +use nexus_db_model::SystemNetworkingSettings; +use nexus_db_model::SystemNetworkingSettingsUpdate; +use omicron_common::api::external::Error; +use omicron_common::api::external::UpdateResult; + +impl DataStore { + /// Read the singleton fleet networking settings row. + pub async fn system_networking_settings_view( + &self, + opctx: &OpContext, + ) -> Result { + /// Any user context can read system network settings, as they + /// must be consulted for some features like jumbo frames. + use nexus_db_schema::schema::system_networking_settings::dsl; + dsl::system_networking_settings + .filter(dsl::singleton.eq(true)) + .first_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + } + + /// Update the singleton fleet networking settings row. Fields left as + /// `None` in the supplied update are not modified. + pub async fn system_networking_settings_update( + &self, + opctx: &OpContext, + external_jumbo_frames_opt_in_enabled: Option, + ) -> UpdateResult { + opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; + + let updates = SystemNetworkingSettingsUpdate { + external_jumbo_frames_opt_in_enabled, + time_modified: Utc::now(), + }; + + use nexus_db_schema::schema::system_networking_settings::dsl; + diesel::update(dsl::system_networking_settings) + .filter(dsl::singleton.eq(true)) + .set(updates) + .returning(SystemNetworkingSettings::as_returning()) + .get_result_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + } +} diff --git a/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs b/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs index 1cf604d102b..9cba5b26186 100644 --- a/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs +++ b/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs @@ -462,6 +462,7 @@ mod test { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ), ) diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 4a79499dda8..e1a594c646b 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -4023,6 +4023,7 @@ mod tests { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ), ) diff --git a/nexus/db-queries/src/db/pub_test_utils/helpers.rs b/nexus/db-queries/src/db/pub_test_utils/helpers.rs index 72b6a05cf47..44da3928a65 100644 --- a/nexus/db-queries/src/db/pub_test_utils/helpers.rs +++ b/nexus/db-queries/src/db/pub_test_utils/helpers.rs @@ -253,6 +253,7 @@ pub async fn create_stopped_instance_record( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ); diff --git a/nexus/db-queries/src/db/queries/external_ip.rs b/nexus/db-queries/src/db/queries/external_ip.rs index 34e9f06baac..909743070f9 100644 --- a/nexus/db-queries/src/db/queries/external_ip.rs +++ b/nexus/db-queries/src/db/queries/external_ip.rs @@ -1044,6 +1044,7 @@ mod tests { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }); let conn = self @@ -2426,6 +2427,7 @@ mod tests { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }); let conn = context diff --git a/nexus/db-queries/src/db/queries/network_interface.rs b/nexus/db-queries/src/db/queries/network_interface.rs index f4affda1547..4d43fcac7bc 100644 --- a/nexus/db-queries/src/db/queries/network_interface.rs +++ b/nexus/db-queries/src/db/queries/network_interface.rs @@ -2004,6 +2004,7 @@ mod tests { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let instance = Instance::new(instance_id, project_id, ¶ms); diff --git a/nexus/db-schema/src/schema.rs b/nexus/db-schema/src/schema.rs index 05d6c96b1db..428aeb57715 100644 --- a/nexus/db-schema/src/schema.rs +++ b/nexus/db-schema/src/schema.rs @@ -467,6 +467,7 @@ table! { boot_disk_id -> Nullable, intended_state -> crate::enums::InstanceIntendedStateEnum, cpu_platform -> Nullable, + enable_jumbo_frames -> Bool, } } @@ -595,6 +596,15 @@ table! { } } +table! { + system_networking_settings(singleton) { + singleton -> Bool, + time_created -> Timestamptz, + time_modified -> Timestamptz, + external_jumbo_frames_opt_in_enabled -> Bool, + } +} + table! { network_interface (id) { id -> Uuid, diff --git a/nexus/external-api/output/nexus_tags.txt b/nexus/external-api/output/nexus_tags.txt index 8035742b535..20f7c265064 100644 --- a/nexus/external-api/output/nexus_tags.txt +++ b/nexus/external-api/output/nexus_tags.txt @@ -295,6 +295,8 @@ networking_switch_port_settings_create POST /v1/system/networking/switch-p networking_switch_port_settings_delete DELETE /v1/system/networking/switch-port-settings networking_switch_port_settings_list GET /v1/system/networking/switch-port-settings networking_switch_port_settings_view GET /v1/system/networking/switch-port-settings/{port} +system_networking_settings_update PUT /v1/system/networking/settings +system_networking_settings_view GET /v1/system/networking/settings API operations found with tag "system/silos" OPERATION ID METHOD URL PATH diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index fdf511da6f8..816503bb994 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -33,6 +33,7 @@ use nexus_types_versions::v2026_01_16_00; use nexus_types_versions::v2026_01_16_01; use nexus_types_versions::v2026_01_22_00; use nexus_types_versions::v2026_01_30_01; +use nexus_types_versions::v2026_01_31_00; use nexus_types_versions::v2026_02_13_01; use nexus_types_versions::v2026_04_16_00; use omicron_common::address::IpRange; @@ -83,6 +84,7 @@ api_versions!([ // | date-based version should be at the top of the list. // v // (next_yyyy_mm_dd_nn, IDENT), + (2026_06_01_00, EXTERNAL_JUMBO_FRAMES), (2026_05_20_00, ADD_CONTACT_SUPPORT_TO_UPDATE_STATUS), (2026_05_08_00, MANUAL_DISK_ADOPTION), (2026_05_07_00, REMOVE_DUPLICATED_NETWORKING_TYPES), @@ -438,6 +440,37 @@ pub trait NexusExternalApi { HttpError, >; + /// Fetch fleet-wide networking settings + #[endpoint { + method = GET, + path = "/v1/system/networking/settings", + tags = ["system/networking"], + versions = VERSION_EXTERNAL_JUMBO_FRAMES.., + }] + async fn system_networking_settings_view( + rqctx: RequestContext, + ) -> Result< + HttpResponseOk, + HttpError, + >; + + /// Update fleet-wide networking settings + #[endpoint { + method = PUT, + path = "/v1/system/networking/settings", + tags = ["system/networking"], + versions = VERSION_EXTERNAL_JUMBO_FRAMES.., + }] + async fn system_networking_settings_update( + rqctx: RequestContext, + new_settings: TypedBody< + latest::system_networking::SystemNetworkingSettingsUpdate, + >, + ) -> Result< + HttpResponseOk, + HttpError, + >; + /// Fetch current silo's IAM policy #[endpoint { method = GET, @@ -3569,26 +3602,78 @@ pub trait NexusExternalApi { method = GET, path = "/v1/instances", tags = ["instances"], + versions = VERSION_EXTERNAL_JUMBO_FRAMES.., }] async fn instance_list( rqctx: RequestContext, query_params: Query< PaginatedByNameOrId, >, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; + + #[endpoint { + operation_id = "instance_list", + method = GET, + path = "/v1/instances", + tags = ["instances"], + versions = ..VERSION_EXTERNAL_JUMBO_FRAMES, + }] + async fn instance_list_v2025_11_20_00( + rqctx: RequestContext, + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + > { + let resp = Self::instance_list(rqctx, query_params).await?; + let inner = resp.0; + Ok(HttpResponseOk(ResultsPage { + items: inner.items.into_iter().map(Into::into).collect(), + next_page: inner.next_page, + })) + } /// Create instance #[endpoint { method = POST, path = "/v1/instances", tags = ["instances"], - versions = VERSION_READ_ONLY_DISKS_NULLABLE.., + versions = VERSION_EXTERNAL_JUMBO_FRAMES.., }] async fn instance_create( rqctx: RequestContext, query_params: Query, new_instance: TypedBody, - ) -> Result, HttpError>; + ) -> Result, HttpError>; + + #[endpoint { + operation_id = "instance_create", + method = POST, + path = "/v1/instances", + tags = ["instances"], + versions = VERSION_READ_ONLY_DISKS_NULLABLE..VERSION_EXTERNAL_JUMBO_FRAMES, + }] + async fn instance_create_v2026_01_31_00( + rqctx: RequestContext, + query_params: Query, + new_instance: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { + let resp = Self::instance_create( + rqctx, + query_params, + new_instance.map(Into::into), + ) + .await?; + Ok(HttpResponseCreated(resp.0.into())) + } #[endpoint { operation_id = "instance_create", @@ -3601,9 +3686,16 @@ pub trait NexusExternalApi { rqctx: RequestContext, query_params: Query, new_instance: TypedBody, - ) -> Result, HttpError> { - Self::instance_create(rqctx, query_params, new_instance.map(Into::into)) - .await + ) -> Result< + HttpResponseCreated, + HttpError, + > { + Self::instance_create_v2026_01_31_00( + rqctx, + query_params, + new_instance.map(Into::into), + ) + .await } /// Create instance @@ -3618,7 +3710,10 @@ pub trait NexusExternalApi { rqctx: RequestContext, query_params: Query, new_instance: TypedBody, - ) -> Result, HttpError> { + ) -> Result< + HttpResponseCreated, + HttpError, + > { Self::instance_create_v2026_01_30_01( rqctx, query_params, @@ -3639,7 +3734,10 @@ pub trait NexusExternalApi { rqctx: RequestContext, query_params: Query, new_instance: TypedBody, - ) -> Result, HttpError> { + ) -> Result< + HttpResponseCreated, + HttpError, + > { Self::instance_create_v2026_01_08_00( rqctx, query_params, @@ -3660,7 +3758,10 @@ pub trait NexusExternalApi { rqctx: RequestContext, query_params: Query, new_instance: TypedBody, - ) -> Result, HttpError> { + ) -> Result< + HttpResponseCreated, + HttpError, + > { let new_instance = new_instance.try_map(TryInto::try_into)?; Self::instance_create_v2026_01_05_00(rqctx, query_params, new_instance) .await @@ -3679,7 +3780,10 @@ pub trait NexusExternalApi { rqctx: RequestContext, query_params: Query, new_instance: TypedBody, - ) -> Result, HttpError> { + ) -> Result< + HttpResponseCreated, + HttpError, + > { let new_instance = new_instance.try_map(TryInto::try_into)?; Self::instance_create_v2026_01_03_00(rqctx, query_params, new_instance) .await @@ -3697,7 +3801,10 @@ pub trait NexusExternalApi { rqctx: RequestContext, query_params: Query, new_instance: TypedBody, - ) -> Result, HttpError> { + ) -> Result< + HttpResponseCreated, + HttpError, + > { Self::instance_create_v2025_12_23_00( rqctx, query_params, @@ -3718,7 +3825,10 @@ pub trait NexusExternalApi { rqctx: RequestContext, query_params: Query, new_instance: TypedBody, - ) -> Result, HttpError> { + ) -> Result< + HttpResponseCreated, + HttpError, + > { Self::instance_create_v2025_12_03_00( rqctx, query_params, @@ -3732,12 +3842,31 @@ pub trait NexusExternalApi { method = GET, path = "/v1/instances/{instance}", tags = ["instances"], + versions = VERSION_EXTERNAL_JUMBO_FRAMES.., }] async fn instance_view( rqctx: RequestContext, query_params: Query, path_params: Path, - ) -> Result, HttpError>; + ) -> Result, HttpError>; + + #[endpoint { + operation_id = "instance_view", + method = GET, + path = "/v1/instances/{instance}", + tags = ["instances"], + versions = ..VERSION_EXTERNAL_JUMBO_FRAMES, + }] + async fn instance_view_v2025_11_20_00( + rqctx: RequestContext, + query_params: Query, + path_params: Path, + ) -> Result, HttpError> + { + let resp = + Self::instance_view(rqctx, query_params, path_params).await?; + Ok(HttpResponseOk(resp.0.into())) + } /// Delete instance #[endpoint { @@ -3756,14 +3885,39 @@ pub trait NexusExternalApi { method = PUT, path = "/v1/instances/{instance}", tags = ["instances"], - versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., + versions = VERSION_EXTERNAL_JUMBO_FRAMES.., }] async fn instance_update( rqctx: RequestContext, query_params: Query, path_params: Path, instance_config: TypedBody, - ) -> Result, HttpError>; + ) -> Result, HttpError>; + + /// Update instance + #[endpoint { + operation_id = "instance_update", + method = PUT, + path = "/v1/instances/{instance}", + tags = ["instances"], + versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES..VERSION_EXTERNAL_JUMBO_FRAMES, + }] + async fn instance_update_v2026_01_08_00( + rqctx: RequestContext, + query_params: Query, + path_params: Path, + instance_config: TypedBody, + ) -> Result, HttpError> + { + let resp = Self::instance_update( + rqctx, + query_params, + path_params, + instance_config.map(Into::into), + ) + .await?; + Ok(HttpResponseOk(resp.0.into())) + } /// Update instance #[endpoint { @@ -3778,8 +3932,9 @@ pub trait NexusExternalApi { query_params: Query, path_params: Path, instance_config: TypedBody, - ) -> Result, HttpError> { - Self::instance_update( + ) -> Result, HttpError> + { + Self::instance_update_v2026_01_08_00( rqctx, query_params, path_params, @@ -3793,36 +3948,99 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances/{instance}/reboot", tags = ["instances"], + versions = VERSION_EXTERNAL_JUMBO_FRAMES.., }] async fn instance_reboot( rqctx: RequestContext, query_params: Query, path_params: Path, - ) -> Result, HttpError>; + ) -> Result, HttpError>; + + #[endpoint { + operation_id = "instance_reboot", + method = POST, + path = "/v1/instances/{instance}/reboot", + tags = ["instances"], + versions = ..VERSION_EXTERNAL_JUMBO_FRAMES, + }] + async fn instance_reboot_v2025_11_20_00( + rqctx: RequestContext, + query_params: Query, + path_params: Path, + ) -> Result< + HttpResponseAccepted, + HttpError, + > { + let resp = + Self::instance_reboot(rqctx, query_params, path_params).await?; + Ok(HttpResponseAccepted(resp.0.into())) + } /// Boot instance #[endpoint { method = POST, path = "/v1/instances/{instance}/start", tags = ["instances"], + versions = VERSION_EXTERNAL_JUMBO_FRAMES.., }] async fn instance_start( rqctx: RequestContext, query_params: Query, path_params: Path, - ) -> Result, HttpError>; + ) -> Result, HttpError>; + + #[endpoint { + operation_id = "instance_start", + method = POST, + path = "/v1/instances/{instance}/start", + tags = ["instances"], + versions = ..VERSION_EXTERNAL_JUMBO_FRAMES, + }] + async fn instance_start_v2025_11_20_00( + rqctx: RequestContext, + query_params: Query, + path_params: Path, + ) -> Result< + HttpResponseAccepted, + HttpError, + > { + let resp = + Self::instance_start(rqctx, query_params, path_params).await?; + Ok(HttpResponseAccepted(resp.0.into())) + } /// Stop instance #[endpoint { method = POST, path = "/v1/instances/{instance}/stop", tags = ["instances"], + versions = VERSION_EXTERNAL_JUMBO_FRAMES.., }] async fn instance_stop( rqctx: RequestContext, query_params: Query, path_params: Path, - ) -> Result, HttpError>; + ) -> Result, HttpError>; + + #[endpoint { + operation_id = "instance_stop", + method = POST, + path = "/v1/instances/{instance}/stop", + tags = ["instances"], + versions = ..VERSION_EXTERNAL_JUMBO_FRAMES, + }] + async fn instance_stop_v2025_11_20_00( + rqctx: RequestContext, + query_params: Query, + path_params: Path, + ) -> Result< + HttpResponseAccepted, + HttpError, + > { + let resp = + Self::instance_stop(rqctx, query_params, path_params).await?; + Ok(HttpResponseAccepted(resp.0.into())) + } /// Fetch instance serial console #[endpoint { diff --git a/nexus/lockstep-api/src/lib.rs b/nexus/lockstep-api/src/lib.rs index 80138d6fee4..d5d918cd570 100644 --- a/nexus/lockstep-api/src/lib.rs +++ b/nexus/lockstep-api/src/lib.rs @@ -42,7 +42,7 @@ use nexus_types::internal_api::views::SupportBundleInfo; use nexus_types::internal_api::views::UpdateStatus; use nexus_types::trust_quorum::TrustQuorumConfig; use nexus_types_versions::latest::headers::RangeRequest; -use omicron_common::api::external::Instance; +use nexus_types_versions::latest::instance::Instance; use omicron_common::api::external::http_pagination::PaginatedById; use omicron_common::api::external::http_pagination::PaginatedByTimeAndId; use omicron_uuid_kinds::*; diff --git a/nexus/src/app/background/tasks/instance_reincarnation.rs b/nexus/src/app/background/tasks/instance_reincarnation.rs index 1b67e86d402..9d197c90d1b 100644 --- a/nexus/src/app/background/tasks/instance_reincarnation.rs +++ b/nexus/src/app/background/tasks/instance_reincarnation.rs @@ -337,6 +337,7 @@ mod test { }; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::instance; + use nexus_types_versions::latest; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::InstanceAutoRestartPolicy; @@ -380,39 +381,39 @@ mod test { // Use the first chunk of the UUID as the name, to avoid conflicts. // Start with a lower ascii character to satisfy the name constraints. let name = name.parse().unwrap(); - let instance = - object_create::<_, omicron_common::api::external::Instance>( - &cptestctx.external_client, - &instances_url, - &instance::InstanceCreate { - identity: IdentityMetadataCreateParams { - name, - description: "It's an instance".into(), - }, - // In this test, we will "leak" sled resources, since we - // munge the database records for the instance without - // deleting its VMM (as we want to explicitly activate the - // reincarnation task, rather than letting the - // `instance-update` saga do so). Therefore, make our - // resource requests as small as possible. - ncpus: 1i64.try_into().unwrap(), - memory: ByteCount::from_gibibytes_u32(2), - hostname: "myhostname".try_into().unwrap(), - user_data: Vec::new(), - network_interfaces: - instance::InstanceNetworkInterfaceAttachment::None, - external_ips: Vec::new(), - disks: Vec::new(), - boot_disk: None, - cpu_platform: None, - ssh_public_keys: None, - start: state == InstanceState::Vmm, - auto_restart_policy, - anti_affinity_groups: Vec::new(), - multicast_groups: Vec::new(), + let instance = object_create::<_, latest::instance::Instance>( + &cptestctx.external_client, + &instances_url, + &instance::InstanceCreate { + identity: IdentityMetadataCreateParams { + name, + description: "It's an instance".into(), }, - ) - .await; + // In this test, we will "leak" sled resources, since we + // munge the database records for the instance without + // deleting its VMM (as we want to explicitly activate the + // reincarnation task, rather than letting the + // `instance-update` saga do so). Therefore, make our + // resource requests as small as possible. + ncpus: 1i64.try_into().unwrap(), + memory: ByteCount::from_gibibytes_u32(2), + hostname: "myhostname".try_into().unwrap(), + user_data: Vec::new(), + network_interfaces: + instance::InstanceNetworkInterfaceAttachment::None, + external_ips: Vec::new(), + disks: Vec::new(), + boot_disk: None, + cpu_platform: None, + ssh_public_keys: None, + start: state == InstanceState::Vmm, + auto_restart_policy, + anti_affinity_groups: Vec::new(), + multicast_groups: Vec::new(), + enable_jumbo_frames: false, + }, + ) + .await; let id = InstanceUuid::from_untyped_uuid(instance.identity.id); let datastore = cptestctx.server.server_context().nexus.datastore(); diff --git a/nexus/src/app/background/tasks/sync_switch_configuration.rs b/nexus/src/app/background/tasks/sync_switch_configuration.rs index 91dcc66b070..2fb4aa26260 100644 --- a/nexus/src/app/background/tasks/sync_switch_configuration.rs +++ b/nexus/src/app/background/tasks/sync_switch_configuration.rs @@ -30,13 +30,25 @@ use dpd_client::{Client as DpdClient, types as DpdTypes}; use futures::FutureExt; use futures::future::BoxFuture; use mg_admin_client::types::{ - AddStaticRoute4Request, AddStaticRoute6Request, ApplyRequest, - BestpathFanoutRequest, BgpPeerConfig, CheckerSource, - DeleteStaticRoute4Request, DeleteStaticRoute6Request, + ApplyRequest, BgpPeerConfig, + UnnumberedBgpPeerConfig as MgUnnumberedBgpPeerConfig, +}; +use mg_api_types::bgp::policy::{ ImportExportPolicy4 as MgImportExportPolicy4, - ImportExportPolicy6 as MgImportExportPolicy6, Ipv4UnicastConfig, - Ipv6UnicastConfig, JitterRange, ShaperSource, StaticRoute4, - StaticRoute4List, StaticRoute6, StaticRoute6List, UnnumberedBgpPeerConfig, + ImportExportPolicy6 as MgImportExportPolicy6, +}; +use mg_api_types::rdb::prefix::{Prefix, Prefix4, Prefix6}; +use mg_api_types::rib::BestpathFanoutRequest; +use mg_api_types::static_routes::{ + AddStaticRoute4Request, AddStaticRoute6Request, StaticRoute4, + StaticRoute4List, StaticRoute6, StaticRoute6List, +}; +use mg_api_types::{ + bgp::config::{ + CheckerSource, Ipv4UnicastConfig, Ipv6UnicastConfig, JitterRange, + ShaperSource, + }, + static_routes::{DeleteStaticRoute4Request, DeleteStaticRoute6Request}, }; use nexus_db_queries::{ context::OpContext, @@ -49,7 +61,6 @@ use omicron_common::{ address::{Ipv6Subnet, get_sled_address}, api::external::{DataPageParams, Name}, }; -use rdb_types::{Prefix, Prefix4, Prefix6}; use serde_json::json; use sled_agent_client::types::HostPortConfig; use sled_agent_types::early_networking::BgpConfig as SledBgpConfig; @@ -78,7 +89,7 @@ use slog_error_chain::InlineErrorChain; use std::{ collections::{HashMap, HashSet, hash_map::Entry}, hash::Hash, - net::{IpAddr, Ipv4Addr, Ipv6Addr}, + net::{IpAddr, Ipv6Addr, SocketAddr}, str::FromStr, sync::{Arc, LazyLock}, }; @@ -549,7 +560,7 @@ impl BackgroundTask for SwitchPortSettingsManager { // desired peer configurations for a given switch port let mut peers: HashMap> = HashMap::new(); - let mut unnumbered_peers: HashMap> = HashMap::new(); + let mut unnumbered_peers: HashMap> = HashMap::new(); for peer in &settings.bgp_peers { let bgp_config_id = peer.bgp_config_id(); @@ -817,7 +828,7 @@ impl BackgroundTask for SwitchPortSettingsManager { // now that the peer passes the above validations, add it to the list for configuration let peer_config = BgpPeerConfig { name: format!("{ip}"), - host: format!("{ip}:179"), + host: SocketAddr::new(ip.into(), 179).to_string(), hold_time: peer.hold_time.into(), idle_hold_time: peer.idle_hold_time.into(), delay_open: peer.delay_open.into(), @@ -866,9 +877,10 @@ impl BackgroundTask for SwitchPortSettingsManager { } // Unnumbered peer - identified by interface RouterPeerType::Unnumbered { router_lifetime } => { - let peer_config = UnnumberedBgpPeerConfig { + let peer_config = MgUnnumberedBgpPeerConfig { name: format!("unnumbered-{}", port.port_name), interface: format!("tfport{}_0", port.port_name), + router_lifetime: router_lifetime.as_u16(), hold_time: peer.hold_time.into(), idle_hold_time: peer.idle_hold_time.into(), delay_open: peer.delay_open.into(), @@ -900,7 +912,6 @@ impl BackgroundTask for SwitchPortSettingsManager { }), deterministic_collision_resolution: false, idle_hold_jitter: None, - router_lifetime: router_lifetime.as_u16(), src_port: None, src_addr: None, }; @@ -998,7 +1009,7 @@ impl BackgroundTask for SwitchPortSettingsManager { "switch_slot" => ?switch_slot, "config" => ?config, ); - if let Err(e) = client.bgp_apply_v2(config).await { + if let Err(e) = client.bgp_apply(config).await { error!(log, "error while applying bgp configuration"; "error" => ?e); } @@ -1848,7 +1859,7 @@ fn build_sled_agent_clients( #[derive(PartialEq, Eq, Hash, Debug)] struct SwitchStaticRouteV4 { - nexthop: Ipv4Addr, + nexthop: IpAddr, prefix: Prefix4, vlan: Option, priority: u8, @@ -2031,7 +2042,7 @@ fn static_routes_in_db( None => DEFAULT_RIB_PRIORITY_STATIC, }; routes.insert(SwitchStaticRoute::V4(SwitchStaticRouteV4 { - nexthop, + nexthop: IpAddr::V4(nexthop), prefix: Prefix4 { value: dst, length: route.dst.prefix(), @@ -2060,6 +2071,21 @@ fn static_routes_in_db( priority, })); } + (IpAddr::V6(nexthop), IpAddr::V4(dst)) => { + let priority = match route.rib_priority { + Some(v) => v.0, + None => DEFAULT_RIB_PRIORITY_STATIC, + }; + routes.insert(SwitchStaticRoute::V4(SwitchStaticRouteV4 { + nexthop: IpAddr::V6(nexthop), + prefix: Prefix4 { + value: dst, + length: route.dst.prefix(), + }, + vlan: route.vid.map(|x| x.0), + priority, + })); + } (nexthop, dst) => { error!(log, "encountered route with ip version mismatch"; "nexthop" => nexthop.to_string(), @@ -2291,7 +2317,7 @@ async fn static_routes_on_switch( }; flattened.insert(SwitchStaticRoute::V4( SwitchStaticRouteV4 { - nexthop: addr, + nexthop: IpAddr::V4(addr), prefix: dst, vlan: p.vlan_id, priority: p.rib_priority, @@ -2299,22 +2325,33 @@ async fn static_routes_on_switch( )); } IpAddr::V6(addr) => { - let Ok(dst) = destination.parse() else { - error!( - log, - "failed to parse static route destination: \ - {destination}" - ); + if let Ok(dst) = destination.parse::() { + flattened.insert(SwitchStaticRoute::V6( + SwitchStaticRouteV6 { + nexthop: addr, + prefix: dst, + vlan: p.vlan_id, + priority: p.rib_priority, + }, + )); continue; }; - flattened.insert(SwitchStaticRoute::V6( - SwitchStaticRouteV6 { - nexthop: addr, - prefix: dst, - vlan: p.vlan_id, - priority: p.rib_priority, - }, - )); + if let Ok(dst) = destination.parse::() { + flattened.insert(SwitchStaticRoute::V4( + SwitchStaticRouteV4 { + nexthop: IpAddr::V6(addr), + prefix: dst, + vlan: p.vlan_id, + priority: p.rib_priority, + }, + )); + continue; + }; + error!( + log, + "failed to parse static route destination: \ + {destination}" + ); } }; } diff --git a/nexus/src/app/bgp.rs b/nexus/src/app/bgp.rs index 53cc41996c5..0ca95b2f294 100644 --- a/nexus/src/app/bgp.rs +++ b/nexus/src/app/bgp.rs @@ -177,7 +177,7 @@ impl super::Nexus { for r in &router_info { let asn = r.asn; - let selector = mg_admin_client::types::ExportedSelector { + let selector = mg_api_types::bgp::session::ExportedSelector { afi: None, asn, peer: None, @@ -199,12 +199,12 @@ impl super::Nexus { for (peer_id, exports) in exported { for ex in exports.iter() { let prefix = match ex { - rdb_types::Prefix::V4(v4) => { + mg_api_types::rdb::prefix::Prefix::V4(v4) => { oxnet::IpNet::V4(oxnet::Ipv4Net::new_unchecked( v4.value, v4.length, )) } - rdb_types::Prefix::V6(v6) => { + mg_api_types::rdb::prefix::Prefix::V6(v6) => { oxnet::IpNet::V6(oxnet::Ipv6Net::new_unchecked( v6.value, v6.length, )) diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index ab82d598213..c5a2653665d 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -569,10 +569,27 @@ impl super::Nexus { boot_disk, cpu_platform, multicast_groups, + enable_jumbo_frames, } = params; check_instance_cpu_memory_sizes(*ncpus, *memory)?; + // Opting in to jumbo frames requires the fleet-wide opt-in. Opting out + // (`Some(false)`) is always allowed. + if matches!(enable_jumbo_frames, Some(true)) { + let settings = self + .db_datastore + .system_networking_settings_view(opctx) + .await?; + if !settings.external_jumbo_frames_opt_in_enabled { + return Err(Error::invalid_request( + "enable_jumbo_frames may only be set to true when the \ + fleet-wide jumbo-frames opt-in is enabled by a fleet \ + administrator", + )); + } + } + let boot_disk_id = match boot_disk.as_ref() { Some(disk) => { let selector = disk::DiskSelector { @@ -611,6 +628,18 @@ impl super::Nexus { .instance_reconfigure(opctx, &authz_instance, update) .await; + // Persist the new jumbo-frames bit if it was supplied. Effective MTU is + // recomputed on the next instance start. + if let Some(enable) = enable_jumbo_frames { + self.datastore() + .instance_set_enable_jumbo_frames( + opctx, + &authz_instance, + *enable, + ) + .await?; + } + // Handle multicast group updates if specified if let Some(multicast_groups) = multicast_groups { self.handle_multicast_group_changes( @@ -675,6 +704,22 @@ impl super::Nexus { ))); } + // Requiring jumbo frames on a new instance requires the fleet-wide + // opt-in. + if params.enable_jumbo_frames { + let settings = self + .db_datastore + .system_networking_settings_view(opctx) + .await?; + if !settings.external_jumbo_frames_opt_in_enabled { + return Err(Error::invalid_request( + "enable_jumbo_frames may only be set on an instance when \ + the fleet-wide jumbo-frames opt-in is enabled by a \ + fleet administrator", + )); + } + } + // Collect ephemeral IP selectors for validation let ephemeral_selectors: Vec<_> = params .external_ips @@ -1636,6 +1681,24 @@ impl super::Nexus { }) .collect(); + // Compute the effective MTU for the instance's primary OPTE port. Jumbo + // frames are only applied when both the fleet-wide opt-in is enabled + // and the per-instance bit is set; otherwise we leave `primary_nic_mtu` + // unset and OPTE applies the default MTU. + let primary_nic_mtu = if db_instance.enable_jumbo_frames { + let settings = self + .db_datastore + .system_networking_settings_view(opctx) + .await?; + if settings.external_jumbo_frames_opt_in_enabled { + Some(omicron_common::address::EXTERNAL_JUMBO_FRAMES_MTU) + } else { + None + } + } else { + None + }; + let local_config = sled_agent_client::types::InstanceSledLocalConfig { hostname, nics, @@ -1650,6 +1713,7 @@ impl super::Nexus { }, delegated_zvols, attached_subnets, + primary_nic_mtu, }; let instance_id = InstanceUuid::from_untyped_uuid(db_instance.id()); @@ -3090,6 +3154,7 @@ mod tests { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let instance_id = InstanceUuid::from_untyped_uuid(Uuid::new_v4()); diff --git a/nexus/src/app/lldp.rs b/nexus/src/app/lldp.rs index 8ddb0f3ec07..ff150dedce8 100644 --- a/nexus/src/app/lldp.rs +++ b/nexus/src/app/lldp.rs @@ -85,12 +85,16 @@ impl super::Nexus { .get_neighbors_stream(&format!("{port}/0"), None) .try_collect() .await - .map_err(|e| { - Error::internal_error(&format!( + .map_err(|e| match e.status() { + Some(http::StatusCode::NOT_FOUND) => Error::not_found_by_name( + omicron_common::api::external::ResourceType::SwitchPort, + port, + ), + _ => Error::internal_error(&format!( "failed to get neighbor list for \ {switch_slot:?}/{port}: {}", InlineErrorChain::new(&e), - )) + )), })?; // Strip out any neighbors seen on previous pages prior to sorting the diff --git a/nexus/src/app/mod.rs b/nexus/src/app/mod.rs index 00f8dc27544..9863cbf7ffd 100644 --- a/nexus/src/app/mod.rs +++ b/nexus/src/app/mod.rs @@ -105,6 +105,7 @@ pub(crate) mod support_bundles; mod switch; mod switch_interface; mod switch_port; +mod system_networking; pub mod test_interfaces; mod trust_quorum; mod update; diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index 0ed1084c763..32df6156836 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -651,6 +651,15 @@ impl super::Nexus { ) .await?; + // Persist the fleet-wide jumbo-frames opt-in. The singleton row was + // seeded by the schema migration; we update it here in case RSS shipped + // a non-default value. + if request.external_jumbo_frames_opt_in_enabled { + self.db_datastore + .system_networking_settings_update(opctx, Some(true)) + .await?; + } + // Note: Service firewall rules are propagated on a best-effort basis // in Server::start() via attempt_ip_allowlist_plumbing(). OPTE's // default-deny policy ensures Nexus remains unreachable until the diff --git a/nexus/src/app/sagas/instance_create.rs b/nexus/src/app/sagas/instance_create.rs index afe911c95ca..8c3367e8412 100644 --- a/nexus/src/app/sagas/instance_create.rs +++ b/nexus/src/app/sagas/instance_create.rs @@ -1548,6 +1548,7 @@ pub mod test { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, boundary_switches: HashSet::from([SwitchSlot::Switch0]), } diff --git a/nexus/src/app/sagas/instance_delete.rs b/nexus/src/app/sagas/instance_delete.rs index 86463c55537..9d24df7f363 100644 --- a/nexus/src/app/sagas/instance_delete.rs +++ b/nexus/src/app/sagas/instance_delete.rs @@ -315,6 +315,7 @@ mod test { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, } } diff --git a/nexus/src/app/sagas/instance_migrate.rs b/nexus/src/app/sagas/instance_migrate.rs index a713a8335d3..8230a718a12 100644 --- a/nexus/src/app/sagas/instance_migrate.rs +++ b/nexus/src/app/sagas/instance_migrate.rs @@ -624,6 +624,7 @@ mod tests { }; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::instance as instance_types; + use nexus_types_versions::latest; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, InstanceCpuCount, }; @@ -642,7 +643,7 @@ mod tests { async fn create_instance( client: &ClientTestContext, - ) -> omicron_common::api::external::Instance { + ) -> latest::instance::Instance { let instances_url = format!("/v1/instances?project={}", PROJECT_NAME); object_create( client, @@ -667,6 +668,7 @@ mod tests { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ) .await diff --git a/nexus/src/app/sagas/instance_start.rs b/nexus/src/app/sagas/instance_start.rs index 0899ffcd474..9c01f310631 100644 --- a/nexus/src/app/sagas/instance_start.rs +++ b/nexus/src/app/sagas/instance_start.rs @@ -1179,6 +1179,7 @@ mod test { use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::{instance as instance_types, networking}; use nexus_types::identity::Resource; + use nexus_types_versions::latest; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, InstanceCpuCount, Name, }; @@ -1202,7 +1203,7 @@ mod test { async fn create_instance( client: &ClientTestContext, - ) -> omicron_common::api::external::Instance { + ) -> latest::instance::Instance { let instances_url = format!("/v1/instances?project={}", PROJECT_NAME); object_create( client, @@ -1227,6 +1228,7 @@ mod test { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ) .await diff --git a/nexus/src/app/sagas/instance_update/mod.rs b/nexus/src/app/sagas/instance_update/mod.rs index 4660c88b419..44852c0781a 100644 --- a/nexus/src/app/sagas/instance_update/mod.rs +++ b/nexus/src/app/sagas/instance_update/mod.rs @@ -1643,7 +1643,7 @@ mod test { async fn create_instance( client: &ClientTestContext, - ) -> omicron_common::api::external::Instance { + ) -> nexus_types_versions::latest::instance::Instance { let instances_url = format!("/v1/instances?project={}", PROJECT_NAME); object_create( client, @@ -1670,6 +1670,7 @@ mod test { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ) .await diff --git a/nexus/src/app/sagas/snapshot_create.rs b/nexus/src/app/sagas/snapshot_create.rs index 86ddf534c65..89b61a056ed 100644 --- a/nexus/src/app/sagas/snapshot_create.rs +++ b/nexus/src/app/sagas/snapshot_create.rs @@ -1791,9 +1791,9 @@ mod test { use nexus_test_utils::resource_helpers::object_create; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::instance as instance_types; + use nexus_types_versions::latest::instance::Instance; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; - use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceCpuCount; use omicron_common::api::external::Name; use omicron_common::api::external::NameOrId; @@ -2195,6 +2195,7 @@ mod test { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ) .await; diff --git a/nexus/src/app/system_networking.rs b/nexus/src/app/system_networking.rs new file mode 100644 index 00000000000..a369f843503 --- /dev/null +++ b/nexus/src/app/system_networking.rs @@ -0,0 +1,43 @@ +// 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/. + +//! Fleet-wide networking settings. + +use nexus_db_queries::authz; +use nexus_db_queries::context::OpContext; +use nexus_types::external_api::system_networking; +use omicron_common::api::external::Error; + +impl super::Nexus { + pub(crate) async fn system_networking_settings_view( + &self, + opctx: &OpContext, + ) -> Result { + opctx.authorize(authz::Action::Read, &authz::FLEET).await?; + let settings = + self.db_datastore.system_networking_settings_view(opctx).await?; + Ok(system_networking::SystemNetworkingSettings { + external_jumbo_frames_opt_in_enabled: settings + .external_jumbo_frames_opt_in_enabled, + }) + } + + pub(crate) async fn system_networking_settings_update( + &self, + opctx: &OpContext, + params: &system_networking::SystemNetworkingSettingsUpdate, + ) -> Result { + let settings = self + .db_datastore + .system_networking_settings_update( + opctx, + params.external_jumbo_frames_opt_in_enabled, + ) + .await?; + Ok(system_networking::SystemNetworkingSettings { + external_jumbo_frames_opt_in_enabled: settings + .external_jumbo_frames_opt_in_enabled, + }) + } +} diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index b0fa2ab8408..e7287a53cb0 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -45,8 +45,8 @@ use nexus_types::external_api::{ external_subnet, floating_ip, hardware, identity_provider, image, instance, internet_gateway, ip_pool, metrics, multicast, networking, oxql, path_params, policy, probe, project, rack, scim, silo, sled, snapshot, - ssh_key, subnet_pool, support_bundle, switch, system, timeseries, update, - user, vpc, + ssh_key, subnet_pool, support_bundle, switch, system, system_networking, + timeseries, update, user, vpc, }; // Type imports for API implementations (per RFD 619) use nexus_types::external_api::bfd::BfdStatus; @@ -83,7 +83,6 @@ use omicron_common::api::external::AntiAffinityGroupMember; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Disk; use omicron_common::api::external::Error; -use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceNetworkInterface; use omicron_common::api::external::InternalContext; use omicron_common::api::external::LldpNeighbor; @@ -170,6 +169,47 @@ impl NexusExternalApi for NexusExternalApiImpl { .await } + async fn system_networking_settings_view( + rqctx: RequestContext, + ) -> Result< + HttpResponseOk, + HttpError, + > { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let opctx = + crate::context::op_context_for_external_api(&rqctx).await?; + let settings = + nexus.system_networking_settings_view(&opctx).await?; + Ok(HttpResponseOk(settings)) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn system_networking_settings_update( + rqctx: RequestContext, + new_settings: TypedBody< + system_networking::SystemNetworkingSettingsUpdate, + >, + ) -> Result< + HttpResponseOk, + HttpError, + > { + audit_and_time(&rqctx, |opctx, nexus| async move { + let new_settings = new_settings.into_inner(); + let settings = nexus + .system_networking_settings_update(&opctx, &new_settings) + .await?; + Ok(HttpResponseOk(settings)) + }) + .await + } + async fn policy_view( rqctx: RequestContext, ) -> Result>, HttpError> @@ -2678,7 +2718,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_list( rqctx: RequestContext, query_params: Query>, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -2713,7 +2754,7 @@ impl NexusExternalApi for NexusExternalApiImpl { rqctx: RequestContext, query_params: Query, new_instance: TypedBody, - ) -> Result, HttpError> { + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let project_selector = query_params.into_inner(); let new_instance_params = new_instance.into_inner(); @@ -2735,7 +2776,7 @@ impl NexusExternalApi for NexusExternalApiImpl { rqctx: RequestContext, query_params: Query, path_params: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; let path = path_params.into_inner(); @@ -2789,7 +2830,7 @@ impl NexusExternalApi for NexusExternalApiImpl { query_params: Query, path_params: Path, instance_config: TypedBody, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let query = query_params.into_inner(); let path = path_params.into_inner(); let instance_config = instance_config.into_inner(); @@ -2816,7 +2857,7 @@ impl NexusExternalApi for NexusExternalApiImpl { rqctx: RequestContext, query_params: Query, path_params: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let path = path_params.into_inner(); let query = query_params.into_inner(); let instance_selector = instance::InstanceSelector { @@ -2837,7 +2878,7 @@ impl NexusExternalApi for NexusExternalApiImpl { rqctx: RequestContext, query_params: Query, path_params: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let path = path_params.into_inner(); let query = query_params.into_inner(); let instance_selector = instance::InstanceSelector { @@ -2863,7 +2904,7 @@ impl NexusExternalApi for NexusExternalApiImpl { rqctx: RequestContext, query_params: Query, path_params: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let path = path_params.into_inner(); let query = query_params.into_inner(); let instance_selector = instance::InstanceSelector { diff --git a/nexus/src/lib.rs b/nexus/src/lib.rs index 1774bf35b28..8293bd9b9fc 100644 --- a/nexus/src/lib.rs +++ b/nexus/src/lib.rs @@ -480,6 +480,7 @@ impl nexus_test_interface::NexusServer for Server { bfd: Vec::new(), }, allowed_source_ips: AllowedSourceIps::Any, + external_jumbo_frames_opt_in_enabled: false, // Insert a fake trust quorum config such that existing // sleds will never be present. // diff --git a/nexus/src/lockstep_api/http_entrypoints.rs b/nexus/src/lockstep_api/http_entrypoints.rs index 6e62aa36a3d..04df1612332 100644 --- a/nexus/src/lockstep_api/http_entrypoints.rs +++ b/nexus/src/lockstep_api/http_entrypoints.rs @@ -53,8 +53,8 @@ use nexus_types::internal_api::views::UpdateStatus; use nexus_types::internal_api::views::to_list; use nexus_types::trust_quorum::TrustQuorumConfig; use nexus_types_versions::latest::headers::RangeRequest; +use nexus_types_versions::latest::instance::Instance; use omicron_common::api::external::Error; -use omicron_common::api::external::Instance; use omicron_common::api::external::http_pagination::PaginatedById; use omicron_common::api::external::http_pagination::PaginatedByTimeAndId; use omicron_common::api::external::http_pagination::ScanById; diff --git a/nexus/test-utils/Cargo.toml b/nexus/test-utils/Cargo.toml index 4cd399aa910..f6c08024809 100644 --- a/nexus/test-utils/Cargo.toml +++ b/nexus/test-utils/Cargo.toml @@ -39,6 +39,7 @@ nexus-db-queries = { workspace = true, features = [ "testing" ] } nexus-lockstep-client.workspace = true nexus-test-interface.workspace = true nexus-types.workspace = true +nexus-types-versions.workspace = true omicron-cockroach-admin.workspace = true omicron-common.workspace = true omicron-passwords.workspace = true diff --git a/nexus/test-utils/src/resource_helpers.rs b/nexus/test-utils/src/resource_helpers.rs index ace0d2f265a..2b2a4739e6e 100644 --- a/nexus/test-utils/src/resource_helpers.rs +++ b/nexus/test-utils/src/resource_helpers.rs @@ -57,6 +57,7 @@ use nexus_types::external_api::vpc; use nexus_types::external_api::vpc::{Vpc, VpcRouter, VpcSubnet}; use nexus_types::identity::Resource; use nexus_types::internal_api::params as internal_params; +use nexus_types_versions::latest::instance::Instance; use omicron_common::api::external::AffinityPolicy; use omicron_common::api::external::ByteCount; use omicron_common::api::external::Disk; @@ -64,7 +65,6 @@ use omicron_common::api::external::Error; use omicron_common::api::external::FailureDomain; use omicron_common::api::external::Generation; use omicron_common::api::external::IdentityMetadataCreateParams; -use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceAutoRestartPolicy; use omicron_common::api::external::InstanceCpuCount; use omicron_common::api::external::InstanceCpuPlatform; @@ -940,6 +940,7 @@ pub async fn create_instance_with( auto_restart_policy, anti_affinity_groups: Vec::new(), multicast_groups, + enable_jumbo_frames: false, }, ) .await diff --git a/nexus/tests/integration_tests/affinity.rs b/nexus/tests/integration_tests/affinity.rs index 0f959ecfc1f..386e25152de 100644 --- a/nexus/tests/integration_tests/affinity.rs +++ b/nexus/tests/integration_tests/affinity.rs @@ -29,6 +29,7 @@ use nexus_types::external_api::affinity::AntiAffinityGroup; use nexus_types::external_api::instance; use nexus_types::external_api::sled::Sled; use nexus_types::external_api::sled::SledInstance; +use nexus_types_versions::latest; use omicron_common::api::external; use omicron_common::api::external::AffinityGroupMember; use omicron_common::api::external::AntiAffinityGroupMember; @@ -58,7 +59,7 @@ impl ProjectScopedApiHelper<'_, T> { async fn create_stopped_instance( &self, instance_name: &str, - ) -> external::Instance { + ) -> latest::instance::Instance { create_instance_with( &self.client, &self.project.as_ref().expect("Need to specify project name"), @@ -288,8 +289,8 @@ impl<'a> ApiHelper<'a> { async fn start_instance( &self, - instance: &external::Instance, - ) -> external::Instance { + instance: &latest::instance::Instance, + ) -> latest::instance::Instance { let uri = format!("/v1/instances/{}/start", instance.identity.id); NexusRequest::new( diff --git a/nexus/tests/integration_tests/disks.rs b/nexus/tests/integration_tests/disks.rs index 810c229f7bc..7bc64eb2f1c 100644 --- a/nexus/tests/integration_tests/disks.rs +++ b/nexus/tests/integration_tests/disks.rs @@ -39,12 +39,12 @@ use nexus_types::external_api::sled; use nexus_types::external_api::snapshot; use nexus_types::identity::Asset; use nexus_types::silo::DEFAULT_SILO_ID; +use nexus_types_versions::latest::instance::Instance; use omicron_common::api::external::ByteCount; use omicron_common::api::external::Disk; use omicron_common::api::external::DiskState; use omicron_common::api::external::DiskType; use omicron_common::api::external::IdentityMetadataCreateParams; -use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceState; use omicron_common::api::external::Name; use omicron_common::api::external::NameOrId; @@ -549,7 +549,7 @@ async fn test_disk_move_between_instances(cptestctx: &ControlPlaneTestContext) { let url_instance_attach_disk = get_disk_attach_url(&instance.identity.name.clone().into()); let url_instance_detach_disk = - get_disk_detach_url(&instance.identity.name.into()); + get_disk_detach_url(&instance.identity.name.clone().into()); // Start attaching the disk to the instance. let attached_disk = disk_post( diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 9a324be5d90..44d5ac7feb0 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -46,6 +46,7 @@ use nexus_types::external_api::ssh_key; use nexus_types::external_api::subnet_pool; use nexus_types::external_api::support_bundle; use nexus_types::external_api::system; +use nexus_types::external_api::system_networking; use nexus_types::external_api::timeseries; use nexus_types::external_api::update; use nexus_types::external_api::vpc; @@ -215,6 +216,8 @@ pub const DEMO_ACCESS_TOKEN_DELETE_URL: &str = // Global policy pub const SYSTEM_POLICY_URL: &'static str = "/v1/system/policy"; +pub const SYSTEM_NETWORKING_SETTINGS_URL: &'static str = + "/v1/system/networking/settings"; // Silo used for testing pub static DEMO_SILO_NAME: LazyLock = @@ -794,6 +797,7 @@ pub static DEMO_INSTANCE_CREATE: LazyLock = auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }); pub static DEMO_STOPPED_INSTANCE_CREATE: LazyLock = LazyLock::new(|| instance::InstanceCreate { @@ -820,6 +824,7 @@ pub static DEMO_STOPPED_INSTANCE_CREATE: LazyLock = auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }); pub static DEMO_INSTANCE_UPDATE: LazyLock = LazyLock::new(|| instance::InstanceUpdate { @@ -829,6 +834,7 @@ pub static DEMO_INSTANCE_UPDATE: LazyLock = ncpus: InstanceCpuCount(1), memory: ByteCount::from_gibibytes_u32(16), multicast_groups: None, + enable_jumbo_frames: None, }); // The instance needs a network interface, too. @@ -1810,6 +1816,25 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( ), ], }, + // Fleet-wide networking settings + VerifyEndpoint { + url: &SYSTEM_NETWORKING_SETTINGS_URL, + visibility: Visibility::Public, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![ + AllowedMethod::Get, + AllowedMethod::Put( + serde_json::to_value( + &system_networking::SystemNetworkingSettingsUpdate { + external_jumbo_frames_opt_in_enabled: Some( + false, + ), + }, + ) + .unwrap(), + ), + ], + }, // IP Pools top-level endpoint VerifyEndpoint { url: &DEMO_IP_POOLS_URL, diff --git a/nexus/tests/integration_tests/external_ips.rs b/nexus/tests/integration_tests/external_ips.rs index 8d4a37198f9..6aa272639d8 100644 --- a/nexus/tests/integration_tests/external_ips.rs +++ b/nexus/tests/integration_tests/external_ips.rs @@ -53,6 +53,7 @@ use nexus_types::external_api::policy::SiloRole; use nexus_types::external_api::project; use nexus_types::external_api::silo; use nexus_types::identity::Resource; +use nexus_types_versions::latest::instance::Instance; use omicron_common::address::IpRange; use omicron_common::address::IpVersion; use omicron_common::address::Ipv4Range; @@ -61,7 +62,6 @@ use omicron_common::address::NUM_SOURCE_NAT_PORTS; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; -use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceCpuCount; use omicron_common::api::external::InstanceState; use omicron_common::api::external::Name; @@ -1173,6 +1173,7 @@ async fn test_floating_ip_attach_fail_between_projects( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, StatusCode::BAD_REQUEST, ) diff --git a/nexus/tests/integration_tests/instances.rs b/nexus/tests/integration_tests/instances.rs index 2aff4004804..cc5f924d835 100644 --- a/nexus/tests/integration_tests/instances.rs +++ b/nexus/tests/integration_tests/instances.rs @@ -67,6 +67,7 @@ use nexus_types::external_api::vpc::VpcSubnet; use nexus_types::identity::Resource; use nexus_types::internal_api::params::InstanceMigrateRequest; use nexus_types::silo::DEFAULT_SILO_ID; +use nexus_types_versions::latest::instance::Instance; use omicron_common::address::IpVersion; use omicron_common::api::external::AffinityPolicy; use omicron_common::api::external::ByteCount; @@ -76,7 +77,6 @@ use omicron_common::api::external::Error; use omicron_common::api::external::FailureDomain; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; -use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceAutoRestartPolicy; use omicron_common::api::external::InstanceCpuCount; use omicron_common::api::external::InstanceCpuPlatform; @@ -280,6 +280,7 @@ async fn test_create_instance_with_bad_hostname_impl( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let mut body: serde_json::Value = serde_json::from_str(&serde_json::to_string(¶ms).unwrap()).unwrap(); @@ -389,6 +390,7 @@ async fn test_instances_create_reboot_halt( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, })) .expect_status(Some(StatusCode::BAD_REQUEST)), ) @@ -2463,6 +2465,7 @@ async fn test_instances_create_stopped_start( multicast_groups: Vec::new(), auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }, ) .await; @@ -2637,6 +2640,7 @@ async fn test_instance_using_image_from_other_project_fails( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, })) .expect_status(Some(StatusCode::BAD_REQUEST)), ) @@ -2706,6 +2710,7 @@ async fn test_instance_create_saga_removes_instance_database_record( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let response = NexusRequest::objects_post( client, @@ -2739,6 +2744,7 @@ async fn test_instance_create_saga_removes_instance_database_record( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let _ = NexusRequest::objects_post( client, @@ -2895,6 +2901,7 @@ async fn test_instance_with_single_explicit_ip_address_impl( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let response = NexusRequest::objects_post( client, @@ -3075,6 +3082,7 @@ async fn test_instance_with_new_custom_network_interfaces( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let response = NexusRequest::objects_post( client, @@ -3207,6 +3215,7 @@ async fn test_instance_create_delete_network_interface( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let response = NexusRequest::objects_post( client, @@ -3485,6 +3494,7 @@ async fn test_instance_update_network_interfaces( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let response = NexusRequest::objects_post( client, @@ -3909,6 +3919,7 @@ async fn cannot_make_new_primary_nic_lacking_ip_stack_for_external_addresses( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let response = NexusRequest::objects_post( client, @@ -4290,6 +4301,7 @@ async fn test_instance_with_multiple_nics_unwinds_completely( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = RequestBuilder::new(client, http::Method::POST, &get_instances_url()) @@ -4366,6 +4378,7 @@ async fn test_attach_one_disk_to_instance(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -4464,6 +4477,7 @@ async fn test_instance_create_attach_disks( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -4571,6 +4585,7 @@ async fn test_instance_create_attach_disks_undo( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -4658,6 +4673,7 @@ async fn test_attach_eight_disks_to_instance( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -4755,6 +4771,7 @@ async fn test_disk_attach_limit(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let url_instances = format!("/v1/instances?project={}", project_name); @@ -4860,6 +4877,7 @@ async fn test_cannot_attach_faulted_disks(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -4954,6 +4972,7 @@ async fn test_disks_detached_when_instance_destroyed( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -5052,6 +5071,7 @@ async fn test_disks_detached_when_instance_destroyed( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -5139,6 +5159,7 @@ async fn test_duplicate_disk_attach_requests_ok( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -5186,6 +5207,7 @@ async fn test_duplicate_disk_attach_requests_ok( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -5246,6 +5268,7 @@ async fn test_cannot_detach_boot_disk(cptestctx: &ControlPlaneTestContext) { multicast_groups: Vec::new(), auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -5309,6 +5332,7 @@ async fn test_cannot_detach_boot_disk(cptestctx: &ControlPlaneTestContext) { ncpus: InstanceCpuCount::try_from(2).unwrap(), memory: ByteCount::from_gibibytes_u32(4), multicast_groups: None, + enable_jumbo_frames: None, }, ) .await; @@ -5385,6 +5409,7 @@ async fn test_updating_running_instance_boot_disk_is_conflict( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -5417,6 +5442,7 @@ async fn test_updating_running_instance_boot_disk_is_conflict( ncpus: InstanceCpuCount::try_from(2).unwrap(), memory: ByteCount::from_gibibytes_u32(4), multicast_groups: None, + enable_jumbo_frames: None, }, http::StatusCode::CONFLICT, ) @@ -5439,6 +5465,7 @@ async fn test_updating_running_instance_boot_disk_is_conflict( ncpus: InstanceCpuCount::try_from(2).unwrap(), memory: ByteCount::from_gibibytes_u32(4), multicast_groups: None, + enable_jumbo_frames: None, }, ) .await; @@ -5463,6 +5490,7 @@ async fn test_updating_missing_instance_is_not_found( ncpus: InstanceCpuCount::try_from(0).unwrap(), memory: ByteCount::from_gibibytes_u32(0), multicast_groups: None, + enable_jumbo_frames: None, }, http::StatusCode::NOT_FOUND, ) @@ -5558,6 +5586,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: None, anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -5585,6 +5614,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { ncpus: initial_ncpus, memory: initial_memory, multicast_groups: None, + enable_jumbo_frames: None, }; // Resizing the instance immediately will error; the instance is running. @@ -5777,6 +5807,7 @@ async fn test_auto_restart_policy_can_be_changed( auto_restart_policy: None, anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -5805,6 +5836,7 @@ async fn test_auto_restart_policy_can_be_changed( ncpus: InstanceCpuCount::try_from(2).unwrap(), memory: ByteCount::from_gibibytes_u32(4), multicast_groups: None, + enable_jumbo_frames: None, }), ) .await; @@ -5853,6 +5885,7 @@ async fn test_cpu_platform_can_be_changed(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: None, anti_affinity_groups: Vec::new(), multicast_groups: vec![], + enable_jumbo_frames: false, }; let builder = @@ -5881,6 +5914,7 @@ async fn test_cpu_platform_can_be_changed(cptestctx: &ControlPlaneTestContext) { ncpus: InstanceCpuCount::try_from(2).unwrap(), memory: ByteCount::from_gibibytes_u32(4), multicast_groups: None, + enable_jumbo_frames: None, }), ) .await; @@ -5951,6 +5985,7 @@ async fn test_boot_disk_can_be_changed(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -5979,6 +6014,7 @@ async fn test_boot_disk_can_be_changed(cptestctx: &ControlPlaneTestContext) { ncpus: InstanceCpuCount::try_from(2).unwrap(), memory: ByteCount::from_gibibytes_u32(4), multicast_groups: None, + enable_jumbo_frames: None, }, ) .await; @@ -6026,6 +6062,7 @@ async fn test_boot_disk_must_be_attached(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -6051,6 +6088,7 @@ async fn test_boot_disk_must_be_attached(cptestctx: &ControlPlaneTestContext) { ncpus: InstanceCpuCount::try_from(2).unwrap(), memory: ByteCount::from_gibibytes_u32(4), multicast_groups: None, + enable_jumbo_frames: None, }, http::StatusCode::CONFLICT, ) @@ -6086,6 +6124,7 @@ async fn test_boot_disk_must_be_attached(cptestctx: &ControlPlaneTestContext) { ncpus: InstanceCpuCount::try_from(2).unwrap(), memory: ByteCount::from_gibibytes_u32(4), multicast_groups: None, + enable_jumbo_frames: None, }, ) .await; @@ -6125,6 +6164,7 @@ async fn test_instances_memory_rejected_less_than_min_memory_size( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let error = NexusRequest::new( @@ -6181,6 +6221,7 @@ async fn test_instances_memory_not_divisible_by_min_memory_size( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let error = NexusRequest::new( @@ -6237,6 +6278,7 @@ async fn test_instances_memory_greater_than_max_size( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let error = NexusRequest::new( @@ -6346,6 +6388,7 @@ async fn test_instance_create_with_anti_affinity_groups( cpu_platform: None, auto_restart_policy: Default::default(), anti_affinity_groups: anti_affinity_groups_param, + enable_jumbo_frames: false, }; let builder = @@ -6418,6 +6461,7 @@ async fn test_instance_create_with_duplicate_anti_affinity_groups( cpu_platform: None, auto_restart_policy: Default::default(), anti_affinity_groups: anti_affinity_groups_param, + enable_jumbo_frames: false, }; let builder = @@ -6491,6 +6535,7 @@ async fn test_instance_create_with_anti_affinity_groups_that_do_not_exist( cpu_platform: None, auto_restart_policy: Default::default(), anti_affinity_groups: anti_affinity_groups_param, + enable_jumbo_frames: false, }; let error = object_create_error( @@ -6577,6 +6622,7 @@ async fn test_instance_create_with_ssh_keys( cpu_platform: None, auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -6629,6 +6675,7 @@ async fn test_instance_create_with_ssh_keys( cpu_platform: None, auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -6680,6 +6727,7 @@ async fn test_instance_create_with_ssh_keys( cpu_platform: None, auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; let builder = @@ -6824,6 +6872,7 @@ async fn test_cannot_provision_instance_beyond_cpu_capacity( multicast_groups: Vec::new(), auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; let url_instances = get_instances_url(); @@ -6886,6 +6935,7 @@ async fn test_cannot_provision_instance_beyond_cpu_limit( multicast_groups: Vec::new(), auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; let url_instances = get_instances_url(); @@ -6944,6 +6994,7 @@ async fn test_cannot_provision_instance_beyond_ram_capacity( multicast_groups: Vec::new(), auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; let url_instances = get_instances_url(); @@ -7049,6 +7100,7 @@ async fn test_can_start_instance_with_cpu_platform( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: vec![], + enable_jumbo_frames: false, }; let url_instances = get_instances_url(); @@ -7090,6 +7142,7 @@ async fn test_can_start_instance_with_cpu_platform( ncpus: InstanceCpuCount::try_from(1).unwrap(), memory: ByteCount::from_gibibytes_u32(4), multicast_groups: None, + enable_jumbo_frames: None, }, ) .await; @@ -7164,6 +7217,7 @@ async fn test_cannot_start_instance_with_unsatisfiable_cpu_platform( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: vec![], + enable_jumbo_frames: false, }; let url_instances = get_instances_url(); @@ -7465,6 +7519,7 @@ async fn test_instance_ephemeral_ip_from_correct_pool( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let error = object_create_error( client, @@ -7540,6 +7595,7 @@ async fn test_instance_ephemeral_ip_from_orphan_pool( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; // instance create 404s @@ -7607,6 +7663,7 @@ async fn test_instance_ephemeral_ip_no_default_pool_error( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let url = format!("/v1/instances?project={}", PROJECT_NAME); @@ -7642,6 +7699,7 @@ async fn test_instance_ephemeral_ip_no_default_pool_error( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let error = object_create_error(client, &url, &body, StatusCode::NOT_FOUND).await; @@ -7787,6 +7845,7 @@ async fn test_instance_rejects_three_ephemeral_ips( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let error = object_create_error( client, @@ -7838,6 +7897,7 @@ async fn test_instance_rejects_two_ephemeral_auto_without_version( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let error = object_create_error( client, @@ -7895,6 +7955,7 @@ async fn test_instance_rejects_two_ephemeral_auto_none_with_explicit( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let error = object_create_error( client, @@ -7952,6 +8013,7 @@ async fn test_instance_rejects_two_ephemeral_same_pool( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let error = object_create_error( client, @@ -8113,6 +8175,7 @@ async fn test_instance_create_in_silo(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let url_instances = format!("/v1/instances?project={}", PROJECT_NAME); NexusRequest::objects_post(client, &url_instances, &instance_params) @@ -8298,6 +8361,7 @@ async fn test_instance_create_with_cross_project_subnet( start: false, auto_restart_policy: None, anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; let instances_url_a = format!("/v1/instances?project={}", project_a_name); @@ -8427,6 +8491,7 @@ async fn test_silo_limited_collaborator_cross_project_subnet( start: false, auto_restart_policy: None, anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; let instances_url_a = format!("/v1/instances?project={}", project_a_name); @@ -8494,6 +8559,7 @@ async fn test_silo_limited_collaborator_cross_project_subnet( start: false, auto_restart_policy: None, anti_affinity_groups: Vec::new(), + enable_jumbo_frames: false, }; // Should get 404 Not Found because VPC/subnet lookups are scoped to the @@ -9245,6 +9311,7 @@ async fn test_instance_with_max_disks(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; let instance: Instance = diff --git a/nexus/tests/integration_tests/pantry.rs b/nexus/tests/integration_tests/pantry.rs index a987acf58de..9ba6beb0606 100644 --- a/nexus/tests/integration_tests/pantry.rs +++ b/nexus/tests/integration_tests/pantry.rs @@ -22,11 +22,11 @@ use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::disk; use nexus_types::external_api::path_params; use nexus_types::external_api::snapshot::Snapshot; +use nexus_types_versions::latest::instance::Instance; use omicron_common::api::external::ByteCount; use omicron_common::api::external::Disk; use omicron_common::api::external::DiskState; use omicron_common::api::external::IdentityMetadataCreateParams; -use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceState; use omicron_nexus::Nexus; use omicron_uuid_kinds::GenericUuid; diff --git a/nexus/tests/integration_tests/projects.rs b/nexus/tests/integration_tests/projects.rs index abeef09479a..54c43a4fe0b 100644 --- a/nexus/tests/integration_tests/projects.rs +++ b/nexus/tests/integration_tests/projects.rs @@ -34,9 +34,9 @@ use nexus_types::external_api::project::Project; use nexus_types::external_api::silo::Silo; use nexus_types::external_api::snapshot; use nexus_types::identity::Resource; +use nexus_types_versions::latest::instance::Instance; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; -use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceCpuCount; use omicron_common::api::external::Name; use std::str::FromStr; @@ -184,6 +184,7 @@ async fn test_project_deletion_with_instance( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ) .await; diff --git a/nexus/tests/integration_tests/quotas.rs b/nexus/tests/integration_tests/quotas.rs index 4de4955af88..544ed7ebcd4 100644 --- a/nexus/tests/integration_tests/quotas.rs +++ b/nexus/tests/integration_tests/quotas.rs @@ -119,6 +119,7 @@ impl ResourceAllocator { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ) .authn_as(self.auth.clone()) diff --git a/nexus/tests/integration_tests/snapshots.rs b/nexus/tests/integration_tests/snapshots.rs index 8efc49ff6dd..45cc081e058 100644 --- a/nexus/tests/integration_tests/snapshots.rs +++ b/nexus/tests/integration_tests/snapshots.rs @@ -35,11 +35,11 @@ use nexus_types::external_api::disk; use nexus_types::external_api::instance; use nexus_types::external_api::sled; use nexus_types::external_api::snapshot; +use nexus_types_versions::latest::instance::Instance; use omicron_common::api::external; use omicron_common::api::external::ByteCount; use omicron_common::api::external::DiskState; use omicron_common::api::external::IdentityMetadataCreateParams; -use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceCpuCount; use omicron_common::api::external::Name; use omicron_nexus::app::MIN_DISK_SIZE_BYTES; @@ -143,6 +143,7 @@ async fn test_snapshot_basic(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ) .await; @@ -325,6 +326,7 @@ async fn test_snapshot_stopped_instance(cptestctx: &ControlPlaneTestContext) { auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }, ) .await; diff --git a/nexus/tests/integration_tests/subnet_allocation.rs b/nexus/tests/integration_tests/subnet_allocation.rs index e5986d3b342..f86dfd6bfb0 100644 --- a/nexus/tests/integration_tests/subnet_allocation.rs +++ b/nexus/tests/integration_tests/subnet_allocation.rs @@ -71,6 +71,7 @@ async fn create_instance_expect_failure( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; NexusRequest::new( diff --git a/nexus/tests/integration_tests/utilization.rs b/nexus/tests/integration_tests/utilization.rs index d7917d83b19..5756e6f337b 100644 --- a/nexus/tests/integration_tests/utilization.rs +++ b/nexus/tests/integration_tests/utilization.rs @@ -242,6 +242,7 @@ async fn create_resources_in_test_suite_silo( auto_restart_policy: Default::default(), anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), + enable_jumbo_frames: false, }; NexusRequest::objects_post( diff --git a/nexus/tests/integration_tests/vpcs.rs b/nexus/tests/integration_tests/vpcs.rs index 5f1fc37873b..f193afa584d 100644 --- a/nexus/tests/integration_tests/vpcs.rs +++ b/nexus/tests/integration_tests/vpcs.rs @@ -25,9 +25,9 @@ use nexus_types::external_api::project; use nexus_types::external_api::silo; use nexus_types::external_api::vpc; use nexus_types::identity::{Asset, Resource}; +use nexus_types_versions::latest::instance::Instance; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; -use omicron_common::api::external::Instance; use omicron_common::api::external::{ RouteDestination, RouteTarget, VpcFirewallRuleAction, VpcFirewallRuleDirection, VpcFirewallRuleFilter, VpcFirewallRulePriority, @@ -481,6 +481,7 @@ async fn test_limited_collaborator_can_create_instance( start: true, auto_restart_policy: None, anti_affinity_groups: vec![], + enable_jumbo_frames: false, }, ) .authn_as(AuthnMode::SiloUser(limited_user.id)) diff --git a/nexus/types/src/external_api/mod.rs b/nexus/types/src/external_api/mod.rs index a784bac7989..c83076809e7 100644 --- a/nexus/types/src/external_api/mod.rs +++ b/nexus/types/src/external_api/mod.rs @@ -46,6 +46,7 @@ pub mod subnet_pool; pub mod support_bundle; pub mod switch; pub mod system; +pub mod system_networking; pub mod timeseries; pub mod update; pub mod user; diff --git a/nexus/types/src/external_api/system_networking.rs b/nexus/types/src/external_api/system_networking.rs new file mode 100644 index 00000000000..b094793b152 --- /dev/null +++ b/nexus/types/src/external_api/system_networking.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Fleet-wide networking settings. + +pub use nexus_types_versions::latest::system_networking::*; diff --git a/nexus/types/src/internal_api/params.rs b/nexus/types/src/internal_api/params.rs index 9313cfd2925..206ffd8fdc6 100644 --- a/nexus/types/src/internal_api/params.rs +++ b/nexus/types/src/internal_api/params.rs @@ -205,6 +205,10 @@ pub struct RackInitializationRequest { /// * Trust quorum is not fully complete yet, and we only want this to be /// used in production once it is complete. pub initial_trust_quorum_configuration: Option, + /// Fleet-wide jumbo-frames opt-in (defaults to false). Operators can change + /// this post-init via the Nexus API. + #[serde(default)] + pub external_jumbo_frames_opt_in_enabled: bool, } pub type DnsConfigParams = internal_dns_types::config::DnsConfigParams; diff --git a/nexus/types/versions/Cargo.toml b/nexus/types/versions/Cargo.toml index 6c0a2274c6e..ddffa6b880c 100644 --- a/nexus/types/versions/Cargo.toml +++ b/nexus/types/versions/Cargo.toml @@ -16,6 +16,7 @@ daft.workspace = true dropshot.workspace = true http.workspace = true mg-admin-client.workspace = true +mg-api-types.workspace = true omicron-common.workspace = true omicron-passwords.workspace = true omicron-uuid-kinds.workspace = true diff --git a/nexus/types/versions/src/external_jumbo_frames/instance.rs b/nexus/types/versions/src/external_jumbo_frames/instance.rs new file mode 100644 index 00000000000..6c448d1b1be --- /dev/null +++ b/nexus/types/versions/src/external_jumbo_frames/instance.rs @@ -0,0 +1,338 @@ +// 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/. + +//! Instance types for version EXTERNAL_JUMBO_FRAMES. +//! +//! Adds `enable_jumbo_frames` to `InstanceCreate`, `InstanceUpdate`, and the +//! `Instance` view. For create the field is `bool` (defaults to `false`); for +//! update it's `Option` (omit to leave the value unchanged); for the +//! view it's `bool` reflecting the current configured value. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + ByteCount, Hostname, IdentityMetadata, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceAutoRestartStatus, InstanceCpuCount, + InstanceCpuPlatform, InstanceRuntimeState, NameOrId, Nullable, + ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::v2025_11_20_00::{ + self as initial, + instance::{InstanceDiskAttach, UserData, bool_true}, +}; +use crate::v2026_01_03_00::instance::InstanceNetworkInterfaceAttachment; +use crate::v2026_01_05_00::instance::ExternalIpCreate; +use crate::v2026_01_08_00::multicast::MulticastGroupJoinSpec; +use crate::v2026_01_31_00::disk::DiskCreate; + +use crate::v2026_01_08_00; +use crate::v2026_01_31_00; + +/// Describe the instance's disks at creation time +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum InstanceDiskAttachment { + /// During instance creation, create and attach disks + Create(DiskCreate), + /// During instance creation, attach this disk + Attach(InstanceDiskAttach), +} + +impl From + for InstanceDiskAttachment +{ + fn from(old: v2026_01_31_00::instance::InstanceDiskAttachment) -> Self { + match old { + v2026_01_31_00::instance::InstanceDiskAttachment::Create( + create, + ) => Self::Create(create), + v2026_01_31_00::instance::InstanceDiskAttachment::Attach( + attach, + ) => Self::Attach(attach), + } + } +} + +/// Create-time parameters for an `Instance` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The number of vCPUs to be allocated to the instance + pub ncpus: InstanceCpuCount, + /// The amount of RAM (in bytes) to be allocated to the instance + pub memory: ByteCount, + /// The hostname to be assigned to the instance + pub hostname: Hostname, + /// User data for instance initialization systems (such as cloud-init). + /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / + /// characters with padding). Maximum 32 KiB unencoded data. + #[serde(default, with = "UserData")] + pub user_data: Vec, + /// The network interfaces to be created for this instance. + #[serde(default)] + pub network_interfaces: InstanceNetworkInterfaceAttachment, + /// The external IP addresses provided to this instance. + /// + /// By default, all instances have outbound connectivity, but no inbound + /// connectivity. These external addresses can be used to provide a fixed, + /// known IP address for making inbound connections to the instance. + #[serde(default)] + pub external_ips: Vec, + /// Multicast groups this instance should join at creation. + /// + /// Groups can be specified by name, UUID, or IP address. Non-existent + /// groups are created automatically. + #[serde(default)] + pub multicast_groups: Vec, + /// A list of disks to be attached to the instance. + /// + /// Disk attachments of type "create" will be created, while those of type + /// "attach" must already exist. + /// + /// The order of this list does not guarantee a boot order for the + /// instance. Use the boot_disk attribute to specify a boot disk. When + /// boot_disk is specified it will count against the disk attachment limit. + #[serde(default)] + pub disks: Vec, + /// The disk the instance is configured to boot from. + /// + /// This disk can either be attached if it already exists or created along + /// with the instance. + /// + /// Specifying a boot disk is optional but recommended to ensure + /// predictable boot behavior. The boot disk can be set during instance + /// creation or later if the instance is stopped. The boot disk counts + /// against the disk attachment limit. + /// + /// An instance that does not have a boot disk set will use the boot + /// options specified in its UEFI settings, which are controlled by both + /// the instance's UEFI firmware and the guest operating system. Boot + /// options can change as disks are attached and detached, which may + /// result in an instance that only boots to the EFI shell until a boot + /// disk is set. + #[serde(default)] + pub boot_disk: Option, + /// An allowlist of SSH public keys to be transferred to the instance via + /// cloud-init during instance creation. + /// + /// If not provided, all SSH public keys from the user's profile will be + /// sent. If an empty list is provided, no public keys will be transmitted + /// to the instance. + pub ssh_public_keys: Option>, + /// Should this instance be started upon creation; true by default. + #[serde(default = "bool_true")] + pub start: bool, + /// The auto-restart policy for this instance. + /// + /// This policy determines whether the instance should be automatically + /// restarted by the control plane on failure. If this is `null`, no + /// auto-restart policy will be explicitly configured for this instance, + /// and the control plane will select the default policy when determining + /// whether the instance can be automatically restarted. + /// + /// Currently, the global default auto-restart policy is "best-effort", + /// so instances with `null` auto-restart policies will be automatically + /// restarted. However, in the future, the default policy may be + /// configurable through other mechanisms, such as on a per-project + /// basis. In that case, any configured default policy will be used if + /// this is `null`. + #[serde(default)] + pub auto_restart_policy: Option, + /// Anti-affinity groups to which this instance should be added. + #[serde(default)] + pub anti_affinity_groups: Vec, + /// The CPU platform to be used for this instance. If this is `null`, the + /// instance requires no particular CPU platform; when it is started the + /// instance will have the most general CPU platform supported by the sled + /// it is initially placed on. + #[serde(default)] + pub cpu_platform: Option, + /// Enable jumbo frames (8500 byte MTU) on the instance's primary OPTE + /// interface. Requires the fleet-wide jumbo-frames opt-in to be enabled + /// by an operator; otherwise this field must be `false`. Changes only take + /// effect on the next instance restart. + #[serde(default)] + pub enable_jumbo_frames: bool, +} + +impl From for InstanceCreate { + fn from(old: v2026_01_31_00::instance::InstanceCreate) -> Self { + Self { + identity: old.identity, + ncpus: old.ncpus, + memory: old.memory, + hostname: old.hostname, + user_data: old.user_data, + network_interfaces: old.network_interfaces, + external_ips: old.external_ips, + multicast_groups: old.multicast_groups, + disks: old.disks.into_iter().map(Into::into).collect(), + boot_disk: old.boot_disk.map(Into::into), + ssh_public_keys: old.ssh_public_keys, + start: old.start, + auto_restart_policy: old.auto_restart_policy, + anti_affinity_groups: old.anti_affinity_groups, + cpu_platform: old.cpu_platform, + enable_jumbo_frames: false, + } + } +} + +/// Parameters of an `Instance` that can be reconfigured after creation. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceUpdate { + /// The number of vCPUs to be allocated to the instance + pub ncpus: InstanceCpuCount, + + /// The amount of RAM (in bytes) to be allocated to the instance + pub memory: ByteCount, + + /// The disk the instance is configured to boot from. + /// + /// Setting a boot disk is optional but recommended to ensure predictable + /// boot behavior. The boot disk can be set during instance creation or + /// later if the instance is stopped. The boot disk counts against the + /// disk attachment limit. + /// + /// An instance that does not have a boot disk set will use the boot + /// options specified in its UEFI settings, which are controlled by both + /// the instance's UEFI firmware and the guest operating system. Boot + /// options can change as disks are attached and detached, which may + /// result in an instance that only boots to the EFI shell until a boot + /// disk is set. + pub boot_disk: Nullable, + + /// The auto-restart policy for this instance. + /// + /// This policy determines whether the instance should be automatically + /// restarted by the control plane on failure. If this is `null`, any + /// explicitly configured auto-restart policy will be unset, and the + /// control plane will select the default policy when determining whether + /// the instance can be automatically restarted. + /// + /// Currently, the global default auto-restart policy is "best-effort", + /// so instances with `null` auto-restart policies will be automatically + /// restarted. However, in the future, the default policy may be + /// configurable through other mechanisms, such as on a per-project + /// basis. In that case, any configured default policy will be used if + /// this is `null`. + pub auto_restart_policy: Nullable, + + /// The CPU platform to be used for this instance. If this is `null`, + /// the instance requires no particular CPU platform; when it is started + /// the instance will have the most general CPU platform supported by + /// the sled it is initially placed on. + pub cpu_platform: Nullable, + + /// Multicast groups this instance should join. + /// + /// When specified, this replaces the instance's current multicast group + /// membership with the new set of groups. The instance will leave any + /// groups not listed here and join any new groups that are specified. + /// + /// Each entry can specify the group by name, UUID, or IP address, along with + /// optional source IP filtering for SSM (Source-Specific Multicast). When + /// a group doesn't exist, it will be implicitly created using the default + /// multicast pool (or you can specify `ip_version` to disambiguate if needed). + /// + /// If not provided, the instance's multicast group membership will not + /// be changed. + #[serde(default)] + pub multicast_groups: Option>, + + /// Update the per-instance jumbo-frames opt-in. Setting this to `true` + /// requires the fleet-wide jumbo-frames opt-in to be enabled. Changes only + /// take effect on the next instance restart. + #[serde(default)] + pub enable_jumbo_frames: Option, +} + +impl From for InstanceUpdate { + fn from(old: v2026_01_08_00::instance::InstanceUpdate) -> Self { + Self { + ncpus: old.ncpus, + memory: old.memory, + boot_disk: old.boot_disk, + auto_restart_policy: old.auto_restart_policy, + cpu_platform: old.cpu_platform, + multicast_groups: old.multicast_groups, + enable_jumbo_frames: None, + } + } +} + +/// View of an Instance +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Instance { + // TODO is flattening here the intent in RFD 4? + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// ID for the project containing this instance + pub project_id: Uuid, + + /// Number of CPUs allocated for this instance + pub ncpus: InstanceCpuCount, + /// Memory allocated for this instance + pub memory: ByteCount, + /// RFC1035-compliant hostname for the instance + pub hostname: String, + + /// The ID of the disk used to boot this instance, if a specific one is assigned + pub boot_disk_id: Option, + + #[serde(flatten)] + pub runtime: InstanceRuntimeState, + + #[serde(flatten)] + pub auto_restart_status: InstanceAutoRestartStatus, + + /// The CPU platform for this instance. If this is `null`, the instance + /// requires no particular CPU platform. + pub cpu_platform: Option, + + /// When true, this instance has opted in to jumbo frames (8500 byte MTU) + /// on its primary network interface. The effective MTU also depends on + /// the fleet-wide jumbo-frames opt-in; if that is disabled, the primary + /// interface uses the default MTU regardless of this value. Changes only + /// take effect on the next instance restart. + pub enable_jumbo_frames: bool, +} + +impl From for Instance { + fn from(value: initial::instance::Instance) -> Self { + Self { + identity: value.identity, + project_id: value.project_id, + ncpus: value.ncpus, + memory: value.memory, + hostname: value.hostname, + boot_disk_id: value.boot_disk_id, + runtime: value.runtime, + auto_restart_status: value.auto_restart_status, + cpu_platform: value.cpu_platform, + enable_jumbo_frames: false, + } + } +} + +impl From for initial::instance::Instance { + fn from(value: Instance) -> Self { + Self { + identity: value.identity, + project_id: value.project_id, + ncpus: value.ncpus, + memory: value.memory, + hostname: value.hostname, + boot_disk_id: value.boot_disk_id, + runtime: value.runtime, + auto_restart_status: value.auto_restart_status, + cpu_platform: value.cpu_platform, + } + } +} diff --git a/nexus/types/versions/src/external_jumbo_frames/mod.rs b/nexus/types/versions/src/external_jumbo_frames/mod.rs new file mode 100644 index 00000000000..137d59791f3 --- /dev/null +++ b/nexus/types/versions/src/external_jumbo_frames/mod.rs @@ -0,0 +1,18 @@ +// 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/. + +//! Version `EXTERNAL_JUMBO_FRAMES` of the external Nexus API. +//! +//! This version adds: +//! +//! - `InstanceCreate.enable_jumbo_frames` — per-instance opt-in for jumbo +//! frames (8500 byte MTU) on the primary OPTE interface. +//! - `InstanceUpdate.enable_jumbo_frames` — same disposition, mutable via +//! the existing update endpoint. Changes only take effect on the next +//! instance restart. +//! - `SystemNetworkingSettings` view/update — fleet-wide opt-in gate for +//! the per-instance bit, controlled by fleet admins. + +pub mod instance; +pub mod system_networking; diff --git a/nexus/types/versions/src/external_jumbo_frames/system_networking.rs b/nexus/types/versions/src/external_jumbo_frames/system_networking.rs new file mode 100644 index 00000000000..3834566992a --- /dev/null +++ b/nexus/types/versions/src/external_jumbo_frames/system_networking.rs @@ -0,0 +1,27 @@ +// 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/. + +//! Fleet-wide networking settings. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Fleet-wide networking settings. Only fleet admins may view or modify these +/// settings. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +pub struct SystemNetworkingSettings { + /// When true, end users may opt in to jumbo frames (8500 byte MTU) on the + /// primary interface of an instance. When false, instance-level opt-in is + /// ignored and OPTE ports are created with the default MTU. + pub external_jumbo_frames_opt_in_enabled: bool, +} + +/// Parameters for updating the fleet-wide networking settings. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +pub struct SystemNetworkingSettingsUpdate { + /// Toggle the fleet-wide external jumbo-frames opt-in. Omit to leave the + /// current value unchanged. + #[serde(default)] + pub external_jumbo_frames_opt_in_enabled: Option, +} diff --git a/nexus/types/versions/src/impls/networking.rs b/nexus/types/versions/src/impls/networking.rs index 0831b35c920..ddbcec8479f 100644 --- a/nexus/types/versions/src/impls/networking.rs +++ b/nexus/types/versions/src/impls/networking.rs @@ -21,11 +21,11 @@ impl From for latest::networking::AddressLotBlockCreate { } } -impl From +impl From for latest::networking::BgpPeerState { - fn from(s: mg_admin_client::types::FsmStateKind) -> Self { - use mg_admin_client::types::FsmStateKind; + fn from(s: mg_api_types::bgp::session::FsmStateKind) -> Self { + use mg_api_types::bgp::session::FsmStateKind; match s { FsmStateKind::Idle => Self::Idle, FsmStateKind::Connect => Self::Connect, diff --git a/nexus/types/versions/src/initial/instance.rs b/nexus/types/versions/src/initial/instance.rs index 0997c63561b..050f6c1cb39 100644 --- a/nexus/types/versions/src/initial/instance.rs +++ b/nexus/types/versions/src/initial/instance.rs @@ -6,14 +6,16 @@ use std::net::IpAddr; +use api_identity::ObjectIdentity; use omicron_common::api::external::{ - ByteCount, Hostname, IdentityMetadataCreateParams, - InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, Name, - NameOrId, + ByteCount, Hostname, IdentityMetadata, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceAutoRestartStatus, InstanceCpuCount, + InstanceCpuPlatform, InstanceRuntimeState, Name, NameOrId, ObjectIdentity, }; use oxnet::IpNet; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use uuid::Uuid; use super::disk::DiskCreate; @@ -432,3 +434,34 @@ pub struct InstanceSerialConsoleData { /// of the last byte returned in `data`. pub last_byte_offset: u64, } + +/// View of an Instance +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Instance { + // TODO is flattening here the intent in RFD 4? + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// ID for the project containing this instance + pub project_id: Uuid, + + /// Number of CPUs allocated for this instance + pub ncpus: InstanceCpuCount, + /// Memory allocated for this instance + pub memory: ByteCount, + /// RFC1035-compliant hostname for the instance + pub hostname: String, + + /// The ID of the disk used to boot this instance, if a specific one is assigned + pub boot_disk_id: Option, + + #[serde(flatten)] + pub runtime: InstanceRuntimeState, + + #[serde(flatten)] + pub auto_restart_status: InstanceAutoRestartStatus, + + /// The CPU platform for this instance. If this is `null`, the instance + /// requires no particular CPU platform. + pub cpu_platform: Option, +} diff --git a/nexus/types/versions/src/latest.rs b/nexus/types/versions/src/latest.rs index 443b5f39d33..ef05585a1c1 100644 --- a/nexus/types/versions/src/latest.rs +++ b/nexus/types/versions/src/latest.rs @@ -173,13 +173,13 @@ pub mod instance { pub use crate::v2026_01_05_00::instance::EphemeralIpCreate; pub use crate::v2026_01_05_00::instance::ExternalIpCreate; - pub use crate::v2026_01_08_00::instance::InstanceUpdate; - pub use crate::v2026_01_23_00::instance::EphemeralIpDetachSelector; pub use crate::v2026_01_23_00::instance::ExternalIpDetach; - pub use crate::v2026_01_31_00::instance::InstanceCreate; - pub use crate::v2026_01_31_00::instance::InstanceDiskAttachment; + pub use crate::v2026_06_01_00::instance::Instance; + pub use crate::v2026_06_01_00::instance::InstanceCreate; + pub use crate::v2026_06_01_00::instance::InstanceDiskAttachment; + pub use crate::v2026_06_01_00::instance::InstanceUpdate; } pub mod internet_gateway { @@ -402,6 +402,11 @@ pub mod system { pub use crate::v2025_11_20_00::system::PingStatus; } +pub mod system_networking { + pub use crate::v2026_06_01_00::system_networking::SystemNetworkingSettings; + pub use crate::v2026_06_01_00::system_networking::SystemNetworkingSettingsUpdate; +} + pub mod timeseries { pub use crate::v2025_11_20_00::timeseries::TimeseriesQuery; } diff --git a/nexus/types/versions/src/lib.rs b/nexus/types/versions/src/lib.rs index ceaf0faa94a..17254348207 100644 --- a/nexus/types/versions/src/lib.rs +++ b/nexus/types/versions/src/lib.rs @@ -87,3 +87,5 @@ pub mod v2026_05_07_00; pub mod v2026_05_08_00; #[path = "add_contact_support_to_update_status/mod.rs"] pub mod v2026_05_20_00; +#[path = "external_jumbo_frames/mod.rs"] +pub mod v2026_06_01_00; diff --git a/openapi/bootstrap-agent-lockstep.json b/openapi/bootstrap-agent-lockstep.json index dd93582928d..c2125f53b31 100644 --- a/openapi/bootstrap-agent-lockstep.json +++ b/openapi/bootstrap-agent-lockstep.json @@ -1000,6 +1000,11 @@ "description": "DNS name for the DNS zone delegated to the rack for external DNS", "type": "string" }, + "external_jumbo_frames_opt_in_enabled": { + "description": "When true, end users may opt in to jumbo frames on the primary interface of an instance, and control plane services with external-facing OPTE NICs are brought up with an 8500 byte MTU. Operators can toggle this at runtime via the fleet networking settings API.", + "default": false, + "type": "boolean" + }, "internal_services_ip_pool_ranges": { "description": "Ranges of the service IP pool which may be used for internal services.", "type": "array", diff --git a/openapi/nexus-lockstep.json b/openapi/nexus-lockstep.json index f2cf1bdef6e..53a852d5ac5 100644 --- a/openapi/nexus-lockstep.json +++ b/openapi/nexus-lockstep.json @@ -5264,6 +5264,10 @@ "description": "Human-readable free-form text about a resource", "type": "string" }, + "enable_jumbo_frames": { + "description": "When true, this instance has opted in to jumbo frames (8500 byte MTU) on its primary network interface. The effective MTU also depends on the fleet-wide jumbo-frames opt-in; if that is disabled, the primary interface uses the default MTU regardless of this value. Changes only take effect on the next instance restart.", + "type": "boolean" + }, "hostname": { "description": "RFC1035-compliant hostname for the instance", "type": "string" @@ -5329,6 +5333,7 @@ "required": [ "auto_restart_enabled", "description", + "enable_jumbo_frames", "hostname", "id", "memory", @@ -8284,6 +8289,11 @@ "description": "delegated DNS name for external DNS", "type": "string" }, + "external_jumbo_frames_opt_in_enabled": { + "description": "Fleet-wide jumbo-frames opt-in (defaults to false). Operators can change this post-init via the Nexus API.", + "default": false, + "type": "boolean" + }, "initial_trust_quorum_configuration": { "nullable": true, "description": "Data used to write the initial trust quorum configuration to CRDB\n\nThis is optional for two reasons: * For clusters fewer than 3 nodes, we don't support trust quorum. * Trust quorum is not fully complete yet, and we only want this to be used in production once it is complete.", diff --git a/openapi/nexus/nexus-2026052000.0.0-ced7df.json.gitstub b/openapi/nexus/nexus-2026052000.0.0-ced7df.json.gitstub new file mode 100644 index 00000000000..d265307f58f --- /dev/null +++ b/openapi/nexus/nexus-2026052000.0.0-ced7df.json.gitstub @@ -0,0 +1 @@ +4c614ceb0b05e7ab9b2a1f19504a2328dc87a087:openapi/nexus/nexus-2026052000.0.0-ced7df.json diff --git a/openapi/nexus/nexus-2026052000.0.0-ced7df.json b/openapi/nexus/nexus-2026060100.0.0-2b53ab.json similarity index 99% rename from openapi/nexus/nexus-2026052000.0.0-ced7df.json rename to openapi/nexus/nexus-2026060100.0.0-2b53ab.json index c3b359aed5a..9d98ed60a8f 100644 --- a/openapi/nexus/nexus-2026052000.0.0-ced7df.json +++ b/openapi/nexus/nexus-2026060100.0.0-2b53ab.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "2026052000.0.0" + "version": "2026060100.0.0" }, "paths": { "/device/auth": { @@ -11269,6 +11269,68 @@ } } }, + "/v1/system/networking/settings": { + "get": { + "tags": [ + "system/networking" + ], + "summary": "Fetch fleet-wide networking settings", + "operationId": "system_networking_settings_view", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemNetworkingSettings" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "tags": [ + "system/networking" + ], + "summary": "Update fleet-wide networking settings", + "operationId": "system_networking_settings_update", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemNetworkingSettingsUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemNetworkingSettings" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/system/networking/switch-port-settings": { "get": { "tags": [ @@ -22653,6 +22715,10 @@ "description": "Human-readable free-form text about a resource", "type": "string" }, + "enable_jumbo_frames": { + "description": "When true, this instance has opted in to jumbo frames (8500 byte MTU) on its primary network interface. The effective MTU also depends on the fleet-wide jumbo-frames opt-in; if that is disabled, the primary interface uses the default MTU regardless of this value. Changes only take effect on the next instance restart.", + "type": "boolean" + }, "hostname": { "description": "RFC1035-compliant hostname for the instance", "type": "string" @@ -22718,6 +22784,7 @@ "required": [ "auto_restart_enabled", "description", + "enable_jumbo_frames", "hostname", "id", "memory", @@ -22827,6 +22894,11 @@ "$ref": "#/components/schemas/InstanceDiskAttachment" } }, + "enable_jumbo_frames": { + "description": "Enable jumbo frames (8500 byte MTU) on the instance's primary OPTE interface. Requires the fleet-wide jumbo-frames opt-in to be enabled by an operator; otherwise this field must be `false`. Changes only take effect on the next instance restart.", + "default": false, + "type": "boolean" + }, "external_ips": { "description": "The external IP addresses provided to this instance.\n\nBy default, all instances have outbound connectivity, but no inbound connectivity. These external addresses can be used to provide a fixed, known IP address for making inbound connections to the instance.", "default": [], @@ -23436,6 +23508,12 @@ } ] }, + "enable_jumbo_frames": { + "nullable": true, + "description": "Update the per-instance jumbo-frames opt-in. Setting this to `true` requires the fleet-wide jumbo-frames opt-in to be enabled. Changes only take effect on the next instance restart.", + "default": null, + "type": "boolean" + }, "memory": { "description": "The amount of RAM (in bytes) to be allocated to the instance", "allOf": [ @@ -29582,6 +29660,31 @@ } ] }, + "SystemNetworkingSettings": { + "description": "Fleet-wide networking settings. Only fleet admins may view or modify these settings.", + "type": "object", + "properties": { + "external_jumbo_frames_opt_in_enabled": { + "description": "When true, end users may opt in to jumbo frames (8500 byte MTU) on the primary interface of an instance. When false, instance-level opt-in is ignored and OPTE ports are created with the default MTU.", + "type": "boolean" + } + }, + "required": [ + "external_jumbo_frames_opt_in_enabled" + ] + }, + "SystemNetworkingSettingsUpdate": { + "description": "Parameters for updating the fleet-wide networking settings.", + "type": "object", + "properties": { + "external_jumbo_frames_opt_in_enabled": { + "nullable": true, + "description": "Toggle the fleet-wide external jumbo-frames opt-in. Omit to leave the current value unchanged.", + "default": null, + "type": "boolean" + } + } + }, "TargetRelease": { "description": "View of a system software target release", "type": "object", diff --git a/openapi/nexus/nexus-latest.json b/openapi/nexus/nexus-latest.json index 867f4785de9..856f60013a7 120000 --- a/openapi/nexus/nexus-latest.json +++ b/openapi/nexus/nexus-latest.json @@ -1 +1 @@ -nexus-2026052000.0.0-ced7df.json \ No newline at end of file +nexus-2026060100.0.0-2b53ab.json \ No newline at end of file diff --git a/openapi/sled-agent/sled-agent-40.0.0-600e45.json.gitstub b/openapi/sled-agent/sled-agent-40.0.0-600e45.json.gitstub new file mode 100644 index 00000000000..23b7f916608 --- /dev/null +++ b/openapi/sled-agent/sled-agent-40.0.0-600e45.json.gitstub @@ -0,0 +1 @@ +a3a58cc520fcb12bfad1a946eae784ec08cf8718:openapi/sled-agent/sled-agent-40.0.0-600e45.json diff --git a/openapi/sled-agent/sled-agent-40.0.0-600e45.json b/openapi/sled-agent/sled-agent-41.0.0-d909db.json similarity index 99% rename from openapi/sled-agent/sled-agent-40.0.0-600e45.json rename to openapi/sled-agent/sled-agent-41.0.0-d909db.json index fa6c04f9104..c9997880e35 100644 --- a/openapi/sled-agent/sled-agent-40.0.0-600e45.json +++ b/openapi/sled-agent/sled-agent-41.0.0-d909db.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "40.0.0" + "version": "41.0.0" }, "paths": { "/artifacts": { @@ -6082,6 +6082,13 @@ "items": { "$ref": "#/components/schemas/NetworkInterface" } + }, + "primary_nic_mtu": { + "nullable": true, + "description": "The MTU to apply to the instance's primary OPTE port, in bytes. If `None`, the OPTE default is used (1500). Set when the fleet has enabled external jumbo frames and the instance has opted in.", + "type": "integer", + "format": "uint32", + "minimum": 0 } }, "required": [ diff --git a/openapi/sled-agent/sled-agent-latest.json b/openapi/sled-agent/sled-agent-latest.json index 5f70b30a1ee..9e97a1678ca 120000 --- a/openapi/sled-agent/sled-agent-latest.json +++ b/openapi/sled-agent/sled-agent-latest.json @@ -1 +1 @@ -sled-agent-40.0.0-600e45.json \ No newline at end of file +sled-agent-41.0.0-d909db.json \ No newline at end of file diff --git a/openapi/wicketd.json b/openapi/wicketd.json index 08e0548e44e..5219fb03ab1 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -1636,6 +1636,11 @@ "external_dns_zone_name": { "type": "string" }, + "external_jumbo_frames_opt_in_enabled": { + "description": "Enable the fleet-wide jumbo-frames opt-in. Operators can also toggle this at runtime via the Nexus API after handoff.", + "default": false, + "type": "boolean" + }, "internal_services_ip_pool_ranges": { "type": "array", "items": { @@ -3403,6 +3408,11 @@ "external_dns_zone_name": { "type": "string" }, + "external_jumbo_frames_opt_in_enabled": { + "description": "Enable the fleet-wide jumbo-frames opt-in.", + "default": false, + "type": "boolean" + }, "internal_services_ip_pool_ranges": { "type": "array", "items": { diff --git a/package-manifest.toml b/package-manifest.toml index 5fe19b3c793..908648eaae8 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -683,10 +683,10 @@ source.repo = "maghemite" # `tools/maghemite_openapi_version`. Failing to do so will cause a failure when # building `ddm-admin-client` (which will instruct you to update # `tools/maghemite_openapi_version`). -source.commit = "7696ee48d5ee29a917dea459e281fe2e8ff20513" +source.commit = "db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" # The SHA256 digest is automatically posted to: # https://buildomat.eng.oxide.computer/public/file/oxidecomputer/maghemite/image//mg-ddm-gz.sha256.txt -source.sha256 = "ce52b9094adf0ed567bd3ed1e3ac48ac1c983cc7859adacf4f392e415a1189ad" +source.sha256 = "e74d32c0a206d7b96452abcda709d8a6f6efc4b9963f699274b908f46505433e" output.type = "tarball" [package.mg-ddm] @@ -699,10 +699,10 @@ source.repo = "maghemite" # `tools/maghemite_openapi_version`. Failing to do so will cause a failure when # building `ddm-admin-client` (which will instruct you to update # `tools/maghemite_openapi_version`). -source.commit = "7696ee48d5ee29a917dea459e281fe2e8ff20513" +source.commit = "db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" # The SHA256 digest is automatically posted to: # https://buildomat.eng.oxide.computer/public/file/oxidecomputer/maghemite/image//mg-ddm.sha256.txt -source.sha256 = "23950a4e73a07fa7f087ba3312e4bc5a8981fd9ebad54af2350baaa86ad6bbf3" +source.sha256 = "873fcee405cfffe44621802a9e62e18f49695db7c8671fbc05314a81cfa505c5" output.type = "zone" output.intermediate_only = true @@ -714,10 +714,10 @@ source.repo = "maghemite" # `tools/maghemite_openapi_version`. Failing to do so will cause a failure when # building `ddm-admin-client` (which will instruct you to update # `tools/maghemite_openapi_version`). -source.commit = "7696ee48d5ee29a917dea459e281fe2e8ff20513" +source.commit = "db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" # The SHA256 digest is automatically posted to: # https://buildomat.eng.oxide.computer/public/file/oxidecomputer/maghemite/image//mgd.sha256.txt -source.sha256 = "301d31ca481e4822f69484feacca31dd08a7c4aae87d96641d384bda3178d2f3" +source.sha256 = "5a0c407a91c1fb68af5770a33549e9d1695653f476520562a087eb9f42bd9d25" output.type = "zone" output.intermediate_only = true @@ -725,8 +725,8 @@ output.intermediate_only = true service_name = "lldp" source.type = "prebuilt" source.repo = "lldp" -source.commit = "61479b6922f9112fbe1e722414d2b8055212cb12" -source.sha256 = "8f988c0b0fa3ad4121ab0e825298601035e56c5c054bdc3a1dfb4d6c8fd5b300" +source.commit = "54b266174d4de9628bca9c97b0db176e16f12154" +source.sha256 = "0c56ec13dcb11f724e158281947062aa4e70160b202728e85b05ca765673c819" output.type = "zone" output.intermediate_only = true diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index 308379c85c5..76aa53ad55e 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -1208,6 +1208,33 @@ CREATE TABLE IF NOT EXISTS omicron.public.silo_auth_settings ( -- null means no max: users can tokens that never expire device_token_max_ttl_seconds INT8 CHECK (device_token_max_ttl_seconds > 0) ); + +/* + * Fleet-wide networking settings. Singleton row; see `db_metadata` for an + * explanation of the singleton pattern. + */ +CREATE TABLE IF NOT EXISTS omicron.public.system_networking_settings ( + singleton BOOL NOT NULL PRIMARY KEY, + time_created TIMESTAMPTZ NOT NULL, + time_modified TIMESTAMPTZ NOT NULL, + + -- When true, end users may opt in to jumbo frames (8500 byte MTU) on the + -- primary interface of an instance. When false, the per-instance bit is + -- ignored and ports are created with the default MTU. + external_jumbo_frames_opt_in_enabled BOOL NOT NULL, + + CHECK (singleton = true) +); + +INSERT INTO omicron.public.system_networking_settings ( + singleton, + time_created, + time_modified, + external_jumbo_frames_opt_in_enabled +) VALUES ( + TRUE, NOW(), NOW(), FALSE +) ON CONFLICT DO NOTHING; + /* * Projects */ @@ -1423,6 +1450,15 @@ CREATE TABLE IF NOT EXISTS omicron.public.instance ( */ cpu_platform omicron.public.instance_cpu_platform, + /* + * When set this instance has opted in to jumbo frames (8500 byte MTU) + * on its primary OPTE interface. The effective MTU also depends on the + * fleet-wide setting in `system_networking_settings`; if that flag is off, + * the OPTE port is created with the default MTU regardless of this column. + * Changes to this column only take effect on the next instance restart. + */ + enable_jumbo_frames BOOL NOT NULL, + CONSTRAINT vmm_iff_active_propolis CHECK ( ((state = 'vmm') AND (active_propolis_id IS NOT NULL)) OR ((state != 'vmm') AND (active_propolis_id IS NULL)) @@ -8623,7 +8659,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - (TRUE, NOW(), NOW(), '261.0.0', NULL) + (TRUE, NOW(), NOW(), '262.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; diff --git a/schema/crdb/external-jumbo-frames/up1.sql b/schema/crdb/external-jumbo-frames/up1.sql new file mode 100644 index 00000000000..51c1623c22a --- /dev/null +++ b/schema/crdb/external-jumbo-frames/up1.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS omicron.public.system_networking_settings ( + -- Singleton row for fleet-wide networking settings. See `db_metadata` + -- for an explanation of the singleton pattern. + singleton BOOL NOT NULL PRIMARY KEY, + time_created TIMESTAMPTZ NOT NULL, + time_modified TIMESTAMPTZ NOT NULL, + + -- When true, end users may opt in to jumbo frames (8500 byte MTU) on + -- the primary interface of an instance. When false, the per-instance + -- bit is ignored and ports are created with the default MTU. + external_jumbo_frames_opt_in_enabled BOOL NOT NULL, + + CHECK (singleton = true) +); diff --git a/schema/crdb/external-jumbo-frames/up2.sql b/schema/crdb/external-jumbo-frames/up2.sql new file mode 100644 index 00000000000..6bc442ddc8b --- /dev/null +++ b/schema/crdb/external-jumbo-frames/up2.sql @@ -0,0 +1,8 @@ +INSERT INTO omicron.public.system_networking_settings ( + singleton, + time_created, + time_modified, + external_jumbo_frames_opt_in_enabled +) VALUES ( + TRUE, NOW(), NOW(), FALSE +) ON CONFLICT DO NOTHING; diff --git a/schema/crdb/external-jumbo-frames/up3.sql b/schema/crdb/external-jumbo-frames/up3.sql new file mode 100644 index 00000000000..df72d4bad3b --- /dev/null +++ b/schema/crdb/external-jumbo-frames/up3.sql @@ -0,0 +1,2 @@ +ALTER TABLE omicron.public.instance + ADD COLUMN IF NOT EXISTS enable_jumbo_frames BOOL NOT NULL DEFAULT FALSE; diff --git a/schema/crdb/external-jumbo-frames/up4.sql b/schema/crdb/external-jumbo-frames/up4.sql new file mode 100644 index 00000000000..2c881302854 --- /dev/null +++ b/schema/crdb/external-jumbo-frames/up4.sql @@ -0,0 +1 @@ +ALTER TABLE omicron.public.instance ALTER COLUMN enable_jumbo_frames DROP DEFAULT; diff --git a/sled-agent/api/src/lib.rs b/sled-agent/api/src/lib.rs index 0ee2567f5e1..0b80d2f0300 100644 --- a/sled-agent/api/src/lib.rs +++ b/sled-agent/api/src/lib.rs @@ -21,7 +21,7 @@ use omicron_common::api::internal::{ }; use sled_agent_types_versions::{ latest, v1, v4, v6, v7, v9, v10, v11, v12, v14, v16, v17, v18, v20, v22, - v24, v25, v26, v28, v29, v30, v31, v33, v34, v37, v39, + v24, v25, v26, v28, v29, v30, v31, v32, v33, v34, v37, v39, }; use sled_diagnostics::SledDiagnosticsQueryOutput; use slog_error_chain::InlineErrorChain; @@ -38,6 +38,7 @@ api_versions!([ // | example for the next person. // v // (next_int, IDENT), + (41, ADD_INSTANCE_PRIMARY_NIC_MTU), (40, ADD_FMD_TO_INVENTORY), (39, BOOTSTORE_SERVICE_NAT_GENERATION), (38, RENAME_PORT_FEC_SPEED_TO_LINK_FEC_SPEED), @@ -444,7 +445,7 @@ pub trait SledAgentApi { operation_id = "vmm_register", method = PUT, path = "/vmms/{propolis_id}", - versions = VERSION_MAKE_ALL_EXTERNAL_IP_FIELDS_OPTIONAL.. + versions = VERSION_ADD_INSTANCE_PRIMARY_NIC_MTU.. }] async fn vmm_register( rqctx: RequestContext, @@ -452,6 +453,20 @@ pub trait SledAgentApi { body: TypedBody, ) -> Result, HttpError>; + #[endpoint { + operation_id = "vmm_register", + method = PUT, + path = "/vmms/{propolis_id}", + versions = VERSION_MAKE_ALL_EXTERNAL_IP_FIELDS_OPTIONAL..VERSION_ADD_INSTANCE_PRIMARY_NIC_MTU + }] + async fn vmm_register_v32( + rqctx: RequestContext, + path_params: Path, + body: TypedBody, + ) -> Result, HttpError> { + Self::vmm_register(rqctx, path_params, body.map(Into::into)).await + } + #[endpoint { operation_id = "vmm_register", method = PUT, @@ -463,7 +478,7 @@ pub trait SledAgentApi { path_params: Path, body: TypedBody, ) -> Result, HttpError> { - Self::vmm_register(rqctx, path_params, body.map(Into::into)).await + Self::vmm_register_v32(rqctx, path_params, body.map(Into::into)).await } #[endpoint { diff --git a/sled-agent/bootstrap-agent-lockstep-types/src/lib.rs b/sled-agent/bootstrap-agent-lockstep-types/src/lib.rs index 0f6d3b64d55..7813224da4c 100644 --- a/sled-agent/bootstrap-agent-lockstep-types/src/lib.rs +++ b/sled-agent/bootstrap-agent-lockstep-types/src/lib.rs @@ -78,6 +78,13 @@ pub struct RackInitializeRequest { /// IPs or subnets allowed to make requests to user-facing services pub allowed_source_ips: AllowedSourceIps, + + /// When true, end users may opt in to jumbo frames on the primary interface + /// of an instance, and control plane services with external-facing OPTE + /// NICs are brought up with an 8500 byte MTU. Operators can toggle this at + /// runtime via the fleet networking settings API. + #[serde(default)] + pub external_jumbo_frames_opt_in_enabled: bool, } // This custom debug implementation hides the private keys. @@ -97,6 +104,7 @@ impl std::fmt::Debug for RackInitializeRequest { recovery_silo, rack_network_config, allowed_source_ips, + external_jumbo_frames_opt_in_enabled, } = &self; f.debug_struct("RackInitializeRequest") @@ -114,6 +122,10 @@ impl std::fmt::Debug for RackInitializeRequest { .field("recovery_silo", recovery_silo) .field("rack_network_config", rack_network_config) .field("allowed_source_ips", allowed_source_ips) + .field( + "external_jumbo_frames_opt_in_enabled", + external_jumbo_frames_opt_in_enabled, + ) .finish() } } @@ -156,6 +168,8 @@ struct UnvalidatedRackInitializeRequest { // passing this field. #[serde(default = "default_allowed_source_ips")] allowed_source_ips: AllowedSourceIps, + #[serde(default)] + external_jumbo_frames_opt_in_enabled: bool, } impl TryFrom for RackInitializeRequest { @@ -182,6 +196,8 @@ impl TryFrom for RackInitializeRequest { recovery_silo: value.recovery_silo, rack_network_config: value.rack_network_config, allowed_source_ips: value.allowed_source_ips, + external_jumbo_frames_opt_in_enabled: value + .external_jumbo_frames_opt_in_enabled, }) } } diff --git a/sled-agent/early-networking/Cargo.toml b/sled-agent/early-networking/Cargo.toml index 0ae4cc1428c..4d662b6ea03 100644 --- a/sled-agent/early-networking/Cargo.toml +++ b/sled-agent/early-networking/Cargo.toml @@ -23,7 +23,7 @@ omicron-common.workspace = true omicron-ddm-admin-client.workspace = true omicron-workspace-hack.workspace = true oxnet.workspace = true -rdb-types.workspace = true +mg-api-types.workspace = true sled-agent-types.workspace = true slog.workspace = true slog-error-chain.workspace = true diff --git a/sled-agent/early-networking/src/lib.rs b/sled-agent/early-networking/src/lib.rs index b4e9e6f768b..3cb3421ebdc 100644 --- a/sled-agent/early-networking/src/lib.rs +++ b/sled-agent/early-networking/src/lib.rs @@ -16,18 +16,23 @@ use internal_dns_resolver::{ResolveError, Resolver as DnsResolver}; use internal_dns_types::names::ServiceName; use mg_admin_client::Client as MgdClient; use mg_admin_client::types::{ - AddStaticRoute4Request, AddStaticRoute6Request, ApplyRequest, - BestpathFanoutRequest, CheckerSource, - ImportExportPolicy4 as MgImportExportPolicy4, - ImportExportPolicy6 as MgImportExportPolicy6, JitterRange, ShaperSource, - StaticRoute4, StaticRoute4List, StaticRoute6, StaticRoute6List, + ApplyRequest, BfdPeerConfig as MgBfdPeerConfig, + BgpPeerConfig as MgBgpPeerConfig, + UnnumberedBgpPeerConfig as MgUnnumberedBgpPeerConfig, }; -use mg_admin_client::types::{ - BfdPeerConfig as MgBfdPeerConfig, Ipv4UnicastConfig, +use mg_api_types::bgp::config::{ + CheckerSource, Ipv4UnicastConfig, Ipv6UnicastConfig, JitterRange, + ShaperSource, }; -use mg_admin_client::types::{ - BgpPeerConfig as MgBgpPeerConfig, Ipv6UnicastConfig, - UnnumberedBgpPeerConfig as MgUnnumberedBgpPeerConfig, +use mg_api_types::bgp::policy::{ + ImportExportPolicy4 as MgImportExportPolicy4, + ImportExportPolicy6 as MgImportExportPolicy6, +}; +use mg_api_types::rdb::prefix::{Prefix, Prefix4, Prefix6}; +use mg_api_types::rib::BestpathFanoutRequest; +use mg_api_types::static_routes::{ + AddStaticRoute4Request, AddStaticRoute6Request, StaticRoute4, + StaticRoute4List, StaticRoute6, StaticRoute6List, }; use omicron_common::OMICRON_DPD_TAG; use omicron_common::address::DENDRITE_PORT; @@ -37,7 +42,6 @@ use omicron_common::backoff::{ }; use omicron_ddm_admin_client::DdmError; use oxnet::IpNet; -use rdb_types::{Prefix, Prefix4, Prefix6}; use sled_agent_types::early_networking::{ BfdMode, BgpConfig, BgpPeerConfig, ImportExportPolicy, LinkFec, LinkSpeed, PortConfig, RouterPeerType, SwitchSlot, UplinkAddress, @@ -712,7 +716,7 @@ impl<'a> EarlyNetworkSetup<'a> { fanout: config.max_paths.as_nonzero_u8(), }; - if let Err(e) = mgd.bgp_apply_v2(&request).await { + if let Err(e) = mgd.bgp_apply(&request).await { error!( self.log, "BGP peer configuration failed"; @@ -754,7 +758,20 @@ impl<'a> EarlyNetworkSetup<'a> { length: r.destination.width(), }; let sr = StaticRoute4 { - nexthop, + nexthop: IpAddr::V4(nexthop), + prefix, + vlan_id, + rib_priority, + }; + rq.routes.list.push(sr); + } + (IpAddr::V6(nexthop), IpAddr::V4(dest_addr)) => { + let prefix = Prefix4 { + value: dest_addr, + length: r.destination.width(), + }; + let sr = StaticRoute4 { + nexthop: IpAddr::V6(nexthop), prefix, vlan_id, rib_priority, @@ -774,10 +791,10 @@ impl<'a> EarlyNetworkSetup<'a> { }; rqv6.routes.list.push(sr); } - _ => { + (IpAddr::V4(_), IpAddr::V6(_)) => { error!( self.log, - "nexthop and destination are different address types"; + "v6 destination over v4 nexthop not supported"; "nexthop" => ?r.nexthop, "destination" => ?r.destination.addr(), ); @@ -800,7 +817,7 @@ impl<'a> EarlyNetworkSetup<'a> { self.log, "static route configuration failed"; "error" => ?e, - "configuration" => ?rq, + "configuration" => ?rqv6, ); }; diff --git a/sled-agent/rack-setup/src/plan/service.rs b/sled-agent/rack-setup/src/plan/service.rs index 48de2b1d3e9..d0ea30fc4fa 100644 --- a/sled-agent/rack-setup/src/plan/service.rs +++ b/sled-agent/rack-setup/src/plan/service.rs @@ -1482,6 +1482,7 @@ mod tests { bfd: Vec::new(), }, allowed_source_ips: AllowedSourceIps::Any, + external_jumbo_frames_opt_in_enabled: false, }; (dns_ips.to_vec(), config) diff --git a/sled-agent/rack-setup/src/service.rs b/sled-agent/rack-setup/src/service.rs index a9147694cc6..fe2444cc754 100644 --- a/sled-agent/rack-setup/src/service.rs +++ b/sled-agent/rack-setup/src/service.rs @@ -1063,6 +1063,8 @@ impl ServiceInner { rack_network_config, allowed_source_ips, initial_trust_quorum_configuration, + external_jumbo_frames_opt_in_enabled: config + .external_jumbo_frames_opt_in_enabled, }; let notify_nexus = || async { @@ -2211,6 +2213,7 @@ mod test { bfd: Vec::new(), }, allowed_source_ips: AllowedSourceIps::Any, + external_jumbo_frames_opt_in_enabled: false, }; assert_eq!( diff --git a/sled-agent/src/instance.rs b/sled-agent/src/instance.rs index a009e7f3843..2812d3d533d 100644 --- a/sled-agent/src/instance.rs +++ b/sled-agent/src/instance.rs @@ -568,6 +568,11 @@ struct InstanceRunner { firewall_rules: Vec, dhcp_config: DhcpCfg, + // Effective MTU for the primary NIC's OPTE port. `None` means use the OPTE + // default (1500). Populated by Nexus based on jumbo-frame opt-in (fleet + // flag AND instance bit). + primary_nic_mtu: Option, + // Internal State management state: InstanceStates, running_state: Option, @@ -1925,6 +1930,7 @@ impl Instance { multicast_groups: local_config.multicast_groups, firewall_rules: local_config.firewall_rules, dhcp_config, + primary_nic_mtu: local_config.primary_nic_mtu, state: InstanceStates::new(vmm_runtime, migration_id), running_state: None, nexus_client, @@ -2337,6 +2343,7 @@ impl InstanceRunner { .copied() .map(Into::into) .collect(), + mtu: if nic.primary { self.primary_nic_mtu } else { None }, })?; opte_port_names.push(port.0.name().to_string()); opte_ports.push(port); @@ -3004,6 +3011,7 @@ mod tests { }, delegated_zvols: vec![], attached_subnets: vec![], + primary_nic_mtu: None, }; InstanceInitialState { @@ -3633,6 +3641,7 @@ mod tests { multicast_groups: local_config.multicast_groups, firewall_rules: local_config.firewall_rules, dhcp_config, + primary_nic_mtu: local_config.primary_nic_mtu, state: InstanceStates::new(vmm_runtime, migration_id), running_state: None, nexus_client, diff --git a/sled-agent/src/probe_manager.rs b/sled-agent/src/probe_manager.rs index 2f2d5421204..c0494f2b136 100644 --- a/sled-agent/src/probe_manager.rs +++ b/sled-agent/src/probe_manager.rs @@ -382,6 +382,7 @@ impl ProbeManagerInner { // but probes are supposed to mimic instances as closely as // possible. We should consider if we want to support them here. attached_subnets: vec![], + mtu: None, })?; let installed_zone = ZoneBuilderFactory::new() diff --git a/sled-agent/src/services.rs b/sled-agent/src/services.rs index 2dfdb487c3b..4ec0726661e 100644 --- a/sled-agent/src/services.rs +++ b/sled-agent/src/services.rs @@ -1177,6 +1177,17 @@ impl ServiceManager { dhcp_config: DhcpCfg::default(), // Services do not use attached subnets, only instances. attached_subnets: vec![], + // TODO(RFD 689): plumb the fleet-wide jumbo-frames opt-in + // (`system_networking_settings.external_jumbo_frames_opt_in_enabled`) + // through blueprints / reconfigurator-execution to this point + // so that external-facing service zones (Nexus, ExternalDns, + // BoundaryNtp) are brought up with `EXTERNAL_JUMBO_FRAMES_MTU` + // when the operator has enabled the opt-in. + // + // For now, services always use the default MTU. The + // per-instance jumbo opt-in (the primary user-facing feature + // from RFD 689) is fully wired through `instance_ensure_registered`. + mtu: None, }) .map_err(|err| Error::ServicePortCreation { service: zone_kind, diff --git a/sled-agent/src/sim/server.rs b/sled-agent/src/sim/server.rs index a20acd0ee3c..1eb0b1d1c74 100644 --- a/sled-agent/src/sim/server.rs +++ b/sled-agent/src/sim/server.rs @@ -658,6 +658,7 @@ pub async fn run_standalone_server( }, allowed_source_ips: AllowedSourceIps::Any, initial_trust_quorum_configuration: None, + external_jumbo_frames_opt_in_enabled: false, }; let mut nexus_lockstep_address = config.nexus_address; diff --git a/sled-agent/types/versions/src/add_instance_primary_nic_mtu/instance.rs b/sled-agent/types/versions/src/add_instance_primary_nic_mtu/instance.rs new file mode 100644 index 00000000000..31cb7684ebd --- /dev/null +++ b/sled-agent/types/versions/src/add_instance_primary_nic_mtu/instance.rs @@ -0,0 +1,104 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Instance types for version `ADD_INSTANCE_PRIMARY_NIC_MTU`. + +use std::net::SocketAddr; + +use omicron_common::api::external::Hostname; +use omicron_common::api::internal::shared::DelegatedZvol; +use omicron_common::api::internal::shared::DhcpConfig; +use omicron_uuid_kinds::InstanceUuid; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use uuid::Uuid; + +use crate::v1::instance::InstanceMetadata; +use crate::v1::instance::VmmRuntimeState; +use crate::v7::instance::InstanceMulticastMembership; +use crate::v10::inventory::NetworkInterface; +use crate::v18::attached_subnet::AttachedSubnet; +use crate::v29::instance::VmmSpec; +use crate::v31::instance::ResolvedVpcFirewallRule; +use crate::v32; +use crate::v32::instance::ExternalIpConfig; + +/// The body of a request to ensure that a instance and VMM are known to a sled +/// agent. +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct InstanceEnsureBody { + /// The virtual hardware configuration this virtual machine should have when + /// it is started. + pub vmm_spec: VmmSpec, + + /// Information about the sled-local configuration that needs to be + /// established to make the VM's virtual hardware fully functional. + pub local_config: InstanceSledLocalConfig, + + /// The initial VMM runtime state for the VMM being registered. + pub vmm_runtime: VmmRuntimeState, + + /// The ID of the instance for which this VMM is being created. + pub instance_id: InstanceUuid, + + /// The ID of the migration in to this VMM, if this VMM is being + /// ensured is part of a migration in. If this is `None`, the VMM is not + /// being created due to a migration. + pub migration_id: Option, + + /// The address at which this VMM should serve a Propolis server API. + pub propolis_addr: SocketAddr, + + /// Metadata used to track instance statistics. + pub metadata: InstanceMetadata, +} + +/// Describes sled-local configuration that a sled-agent must establish to make +/// the instance's virtual hardware fully functional. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct InstanceSledLocalConfig { + pub hostname: Hostname, + pub nics: Vec, + pub external_ips: ExternalIpConfig, + pub attached_subnets: Vec, + pub multicast_groups: Vec, + pub firewall_rules: Vec, + pub dhcp_config: DhcpConfig, + pub delegated_zvols: Vec, + /// The MTU to apply to the instance's primary OPTE port, in bytes. If + /// `None`, the OPTE default is used (1500). Set when the fleet has + /// enabled external jumbo frames and the instance has opted in. + pub primary_nic_mtu: Option, +} + +impl From for InstanceEnsureBody { + fn from(old: v32::instance::InstanceEnsureBody) -> Self { + Self { + vmm_spec: old.vmm_spec, + local_config: old.local_config.into(), + vmm_runtime: old.vmm_runtime, + instance_id: old.instance_id, + migration_id: old.migration_id, + propolis_addr: old.propolis_addr, + metadata: old.metadata, + } + } +} + +impl From for InstanceSledLocalConfig { + fn from(old: v32::instance::InstanceSledLocalConfig) -> Self { + Self { + hostname: old.hostname, + nics: old.nics, + external_ips: old.external_ips, + attached_subnets: old.attached_subnets, + multicast_groups: old.multicast_groups, + firewall_rules: old.firewall_rules, + dhcp_config: old.dhcp_config, + delegated_zvols: old.delegated_zvols, + primary_nic_mtu: None, + } + } +} diff --git a/sled-agent/types/versions/src/add_instance_primary_nic_mtu/mod.rs b/sled-agent/types/versions/src/add_instance_primary_nic_mtu/mod.rs new file mode 100644 index 00000000000..14ea508c0e7 --- /dev/null +++ b/sled-agent/types/versions/src/add_instance_primary_nic_mtu/mod.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/. + +//! Version `ADD_INSTANCE_PRIMARY_NIC_MTU` of the Sled Agent API. +//! +//! This version adds an MTU field for the instance's primary OPTE NIC so +//! Nexus can request an 8500 byte MTU when jumbo frames are enabled. + +pub mod instance; diff --git a/sled-agent/types/versions/src/latest.rs b/sled-agent/types/versions/src/latest.rs index b4529cf1d27..63fa725d91a 100644 --- a/sled-agent/types/versions/src/latest.rs +++ b/sled-agent/types/versions/src/latest.rs @@ -118,8 +118,8 @@ pub mod instance { pub use crate::v32::instance::ExternalIps; pub use crate::v32::instance::ExternalIpv4Config; pub use crate::v32::instance::ExternalIpv6Config; - pub use crate::v32::instance::InstanceEnsureBody; - pub use crate::v32::instance::InstanceSledLocalConfig; + pub use crate::v41::instance::InstanceEnsureBody; + pub use crate::v41::instance::InstanceSledLocalConfig; } pub mod inventory { diff --git a/sled-agent/types/versions/src/lib.rs b/sled-agent/types/versions/src/lib.rs index 27b112414ab..25a268b12bc 100644 --- a/sled-agent/types/versions/src/lib.rs +++ b/sled-agent/types/versions/src/lib.rs @@ -87,6 +87,8 @@ pub mod v39; pub mod v4; #[path = "add_fmd_to_inventory/mod.rs"] pub mod v40; +#[path = "add_instance_primary_nic_mtu/mod.rs"] +pub mod v41; #[path = "add_probe_put_endpoint/mod.rs"] pub mod v6; #[path = "multicast_support/mod.rs"] diff --git a/smf/sled-agent/non-gimlet/config-rss.toml b/smf/sled-agent/non-gimlet/config-rss.toml index c121ed4fb09..d6583a98827 100644 --- a/smf/sled-agent/non-gimlet/config-rss.toml +++ b/smf/sled-agent/non-gimlet/config-rss.toml @@ -146,6 +146,13 @@ allow = "any" # Note that single IP addresses must include the netmask as well, so `/32` or # `/128`. +# Enable the fleet-wide jumbo-frames opt-in. When true, end users may opt +# in to jumbo frames on the primary interface of an instance, and control +# plane services with external-facing OPTE NICs are brought up with an 8500 +# byte MTU. Defaults to false. Can also be toggled at runtime via the Nexus +# `/v1/system/networking/settings` endpoint after handoff. +external_jumbo_frames_opt_in_enabled = false + # Configuration for the initial Silo, user, and password. # # You don't need to change the silo or user names. diff --git a/tools/install_opte.sh b/tools/install_opte.sh index 5b32a8c472e..ec13294f948 100755 --- a/tools/install_opte.sh +++ b/tools/install_opte.sh @@ -88,7 +88,7 @@ if [[ "x$OPTE_COMMIT" != "x" ]]; then curl -fL -o "$P5P_PATH" "$P5P_URL" RC=0 - pfexec pkg install -g "$P5P_PATH" driver/network/opte || RC=$? + pfexec pkg install -g "$P5P_PATH" pkg://helios-dev/driver/network/opte || RC=$? if [[ "$RC" -eq 0 ]]; then echo "xde driver installed from override p5p" elif [[ "$RC" -eq 4 ]]; then diff --git a/tools/maghemite_ddm_openapi_version b/tools/maghemite_ddm_openapi_version index 060b3a13efb..7386706d3ce 100644 --- a/tools/maghemite_ddm_openapi_version +++ b/tools/maghemite_ddm_openapi_version @@ -1 +1 @@ -COMMIT="7696ee48d5ee29a917dea459e281fe2e8ff20513" +COMMIT="db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" diff --git a/tools/maghemite_mg_openapi_version b/tools/maghemite_mg_openapi_version index 060b3a13efb..7386706d3ce 100644 --- a/tools/maghemite_mg_openapi_version +++ b/tools/maghemite_mg_openapi_version @@ -1 +1 @@ -COMMIT="7696ee48d5ee29a917dea459e281fe2e8ff20513" +COMMIT="db8fd7089cf58623dae7d8f7fe2fb10dfc2f69b7" diff --git a/tools/maghemite_mgd_checksums b/tools/maghemite_mgd_checksums index 470facaa671..9e40a418499 100644 --- a/tools/maghemite_mgd_checksums +++ b/tools/maghemite_mgd_checksums @@ -1,2 +1,2 @@ -CIDL_SHA256="301d31ca481e4822f69484feacca31dd08a7c4aae87d96641d384bda3178d2f3" -MGD_LINUX_SHA256="95f9759a5fde2784d148c81df2218d29adde1d27fb72d5dbcf534de6450f0f7c" \ No newline at end of file +CIDL_SHA256="5a0c407a91c1fb68af5770a33549e9d1695653f476520562a087eb9f42bd9d25" +MGD_LINUX_SHA256="7c17e72ed4daabf22d933244762518955d3e1aaf3a42951c76ba5359a445a469" \ No newline at end of file diff --git a/tools/opte_version b/tools/opte_version index a126f3ac29f..3d9fedd1e06 100644 --- a/tools/opte_version +++ b/tools/opte_version @@ -1 +1 @@ -0.40.479 +0.41.483 diff --git a/wicket-common/src/example.rs b/wicket-common/src/example.rs index 5abf054d7e8..cb0ea73eecf 100644 --- a/wicket-common/src/example.rs +++ b/wicket-common/src/example.rs @@ -271,6 +271,7 @@ impl ExampleRackSetupData { ntp_servers, rack_network_config: Some(rack_network_config), allowed_source_ips: Some(AllowedSourceIps::Any), + external_jumbo_frames_opt_in_enabled: false, }; for tweak in tweaks { @@ -305,6 +306,7 @@ impl ExampleRackSetupData { .clone() .unwrap(), allowed_source_ips: AllowedSourceIps::Any, + external_jumbo_frames_opt_in_enabled: false, }; Self { diff --git a/wicket-common/src/rack_setup.rs b/wicket-common/src/rack_setup.rs index c2ff6377e01..f44eba185b8 100644 --- a/wicket-common/src/rack_setup.rs +++ b/wicket-common/src/rack_setup.rs @@ -54,6 +54,10 @@ pub struct CurrentRssUserConfigInsensitive { pub external_dns_zone_name: String, pub rack_network_config: Option, pub allowed_source_ips: Option, + /// Enable the fleet-wide jumbo-frames opt-in. Operators can also toggle + /// this at runtime via the Nexus API after handoff. + #[serde(default)] + pub external_jumbo_frames_opt_in_enabled: bool, } /// The portion of `CurrentRssUserConfig` that can be posted in one shot; it is @@ -77,6 +81,9 @@ pub struct PutRssUserConfigInsensitive { pub external_dns_zone_name: String, pub rack_network_config: UserSpecifiedRackNetworkConfig, pub allowed_source_ips: AllowedSourceIps, + /// Enable the fleet-wide jumbo-frames opt-in. + #[serde(default)] + pub external_jumbo_frames_opt_in_enabled: bool, } #[derive( diff --git a/wicket/src/cli/rack_setup/config_template.toml b/wicket/src/cli/rack_setup/config_template.toml index b5bcaa202c8..189d76a3309 100644 --- a/wicket/src/cli/rack_setup/config_template.toml +++ b/wicket/src/cli/rack_setup/config_template.toml @@ -38,6 +38,13 @@ internal_services_ip_pool_ranges = [] # Confirm this list contains all expected sleds before continuing! bootstrap_sleds = [] +# When true, end users may opt in to jumbo frames (8500 byte MTU) on the primary +# interface of an instance, and control plane services with external-facing OPTE +# NICs are brought up with an 8500 byte MTU. Operators can also toggle this at +# runtime via the Nexus `/v1/system/networking/settings` endpoint after rack +# initialization. +external_jumbo_frames_opt_in_enabled = false + # Allowlist of source IPs that can make requests to user-facing services. # # Use the key: diff --git a/wicket/src/cli/rack_setup/config_toml.rs b/wicket/src/cli/rack_setup/config_toml.rs index 97e3f65ca48..c24ef73c9f0 100644 --- a/wicket/src/cli/rack_setup/config_toml.rs +++ b/wicket/src/cli/rack_setup/config_toml.rs @@ -107,6 +107,13 @@ impl TomlTemplate { config.allowed_source_ips.as_ref(), ); + *doc.get_mut("external_jumbo_frames_opt_in_enabled") + .unwrap() + .as_value_mut() + .unwrap() = Value::Boolean(Formatted::new( + config.external_jumbo_frames_opt_in_enabled, + )); + *doc.get_mut("bootstrap_sleds").unwrap().as_array_mut().unwrap() = build_sleds_array(&config.bootstrap_sleds); diff --git a/wicket/src/ui/panes/rack_setup.rs b/wicket/src/ui/panes/rack_setup.rs index 76a1b391441..b1b1963f86c 100644 --- a/wicket/src/ui/panes/rack_setup.rs +++ b/wicket/src/ui/panes/rack_setup.rs @@ -692,6 +692,7 @@ fn rss_config_text<'a>( external_dns_zone_name, rack_network_config, allowed_source_ips, + external_jumbo_frames_opt_in_enabled, } = &config.insensitive; // Special single-line values, where we convert some kind of condition into @@ -707,6 +708,10 @@ fn rss_config_text<'a>( Span::styled("Recovery password set: ", label_style), dyn_span(*recovery_silo_password_set, "Yes", "No"), ])); + spans.push(Line::from(vec![ + Span::styled("External jumbo frames opt-in: ", label_style), + dyn_span(*external_jumbo_frames_opt_in_enabled, "Enabled", "Disabled"), + ])); // List of single-line values, each of which may or may not be set; if it's // set we show its value, and if not we show "Not set" in bad_style. diff --git a/wicket/tests/output/example_non_empty.toml b/wicket/tests/output/example_non_empty.toml index 0537bfdeae4..0182b82c0dc 100644 --- a/wicket/tests/output/example_non_empty.toml +++ b/wicket/tests/output/example_non_empty.toml @@ -50,6 +50,13 @@ bootstrap_sleds = [ 5, # serial 4 5 6 (model model2 revision 5, ::1) ] +# When true, end users may opt in to jumbo frames (8500 byte MTU) on the primary +# interface of an instance, and control plane services with external-facing OPTE +# NICs are brought up with an 8500 byte MTU. Operators can also toggle this at +# runtime via the Nexus `/v1/system/networking/settings` endpoint after rack +# initialization. +external_jumbo_frames_opt_in_enabled = false + # Allowlist of source IPs that can make requests to user-facing services. # # Use the key: diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index 22ce64baf4f..f1bcaebcd8e 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -86,6 +86,7 @@ pub(crate) struct CurrentRssConfig { // Currently these are always TCP-MD5 keys, bgp_auth_keys: BTreeMap>, allowed_source_ips: Option, + external_jumbo_frames_opt_in_enabled: bool, // External certificates are uploaded in two separate actions (cert then // key, or vice versa). Here we store a partial certificate; once we have // both parts, we validate it and promote it to be a member of @@ -310,6 +311,8 @@ impl CurrentRssConfig { .allowed_source_ips .clone() .unwrap_or(AllowedSourceIps::Any), + external_jumbo_frames_opt_in_enabled: self + .external_jumbo_frames_opt_in_enabled, }; Ok(request) @@ -534,6 +537,8 @@ impl CurrentRssConfig { self.external_dns_ips = value.external_dns_ips; self.external_dns_zone_name = value.external_dns_zone_name; self.allowed_source_ips = Some(value.allowed_source_ips); + self.external_jumbo_frames_opt_in_enabled = + value.external_jumbo_frames_opt_in_enabled; // Build a new auth key map, dropping all old keys from the map. let new_bgp_auth_key_ids = @@ -589,6 +594,8 @@ impl From<&'_ CurrentRssConfig> for CurrentRssUserConfig { external_dns_zone_name: rss.external_dns_zone_name.clone(), rack_network_config: rss.rack_network_config.clone(), allowed_source_ips: rss.allowed_source_ips.clone(), + external_jumbo_frames_opt_in_enabled: rss + .external_jumbo_frames_opt_in_enabled, }, } } diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index d13b339e431..30fc7e11979 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -20,6 +20,7 @@ workspace = true [dependencies] ahash = { version = "0.8.12" } aho-corasick = { version = "1.1.4" } +anstream = { version = "0.6.21" } anyhow = { version = "1.0.102", features = ["backtrace"] } base16ct = { version = "0.2.0", default-features = false, features = ["alloc"] } base64 = { version = "0.22.1" } @@ -32,8 +33,8 @@ bytes = { version = "1.11.1", features = ["serde"] } camino = { version = "1.2.2", default-features = false, features = ["serde1"] } chrono = { version = "0.4.44", features = ["serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } -clap = { version = "4.5.60", features = ["cargo", "derive", "env", "wrap_help"] } -clap_builder = { version = "4.5.60", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } +clap = { version = "4.6.1", features = ["cargo", "derive", "env", "wrap_help"] } +clap_builder = { version = "4.6.0", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } const-oid = { version = "0.9.6", default-features = false, features = ["db", "std"] } crossbeam-epoch = { version = "0.9.18" } crossbeam-utils = { version = "0.8.21" } @@ -164,6 +165,7 @@ zip-3b31131e45eafb45 = { package = "zip", version = "0.6.6", default-features = [build-dependencies] ahash = { version = "0.8.12" } aho-corasick = { version = "1.1.4" } +anstream = { version = "0.6.21" } anyhow = { version = "1.0.102", features = ["backtrace"] } base16ct = { version = "0.2.0", default-features = false, features = ["alloc"] } base64 = { version = "0.22.1" } @@ -177,8 +179,8 @@ camino = { version = "1.2.2", default-features = false, features = ["serde1"] } cc = { version = "1.2.56", default-features = false, features = ["parallel"] } chrono = { version = "0.4.44", features = ["serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } -clap = { version = "4.5.60", features = ["cargo", "derive", "env", "wrap_help"] } -clap_builder = { version = "4.5.60", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } +clap = { version = "4.6.1", features = ["cargo", "derive", "env", "wrap_help"] } +clap_builder = { version = "4.6.0", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } const-oid = { version = "0.9.6", default-features = false, features = ["db", "std"] } crossbeam-epoch = { version = "0.9.18" } crossbeam-utils = { version = "0.8.21" }