diff --git a/bin/propolis-server/src/lib/spec/api_spec_latest.rs b/bin/propolis-server/src/lib/spec/api_spec_latest.rs index b3772e5ec..e5ff06d30 100644 --- a/bin/propolis-server/src/lib/spec/api_spec_latest.rs +++ b/bin/propolis-server/src/lib/spec/api_spec_latest.rs @@ -38,6 +38,11 @@ pub(crate) fn latest_to_spec_builder( value: latest::instance_spec::InstanceSpec, ) -> Result { let mut builder = SpecBuilder::with_instance_spec_board(value.board)?; + + if let Some(smbios) = value.smbios { + builder.set_smbios_type1_input(smbios); + } + let mut devices: Vec<(SpecKey, latest::instance_spec::Component)> = vec![]; let mut boot_settings = None; let mut storage_backends: BTreeMap = diff --git a/bin/propolis-server/src/lib/spec/api_spec_v2.rs b/bin/propolis-server/src/lib/spec/api_spec_v2.rs index d7f764177..b72e92258 100644 --- a/bin/propolis-server/src/lib/spec/api_spec_v2.rs +++ b/bin/propolis-server/src/lib/spec/api_spec_v2.rs @@ -73,3 +73,75 @@ pub(crate) fn amend( Ok(()) } + +#[cfg(test)] +mod test { + use propolis_api_types_versions::latest::components::board::{ + Board, Chipset, Cpuid, GuestHypervisorInterface, I440Fx, + }; + use propolis_api_types_versions::latest::{ + self, + instance_spec::{CpuidVendor, SmbiosType1Input}, + }; + use propolis_api_types_versions::{v1, v2, v3, v6}; + + #[test] + fn smbios_type1() { + let smbios_input = SmbiosType1Input { + manufacturer: "a4x2".to_string(), + product_name: "913-0000019".to_string(), + serial_number: "2FAKE000".to_string(), + version: 2, + }; + + let api_spec = latest::instance_spec::InstanceSpec { + board: Board { + cpus: 4, + memory_mb: 512, + chipset: Chipset::I440Fx(I440Fx { enable_pcie: false }), + guest_hv_interface: GuestHypervisorInterface::Bhyve, + // Providing *any* CPUID settings keeps Propolis from querying + // bhyve for defaults to use instead, which requires .. byhve + // *and* VMM access. Provide a (useless) empty CPUID set so this + // test can run on non-illumos test systems. + cpuid: Some(Cpuid { + entries: vec![], + vendor: CpuidVendor::Amd, + }), + }, + components: Default::default(), + smbios: Some(smbios_input.clone()), + }; + + let spec = + crate::spec::api_spec_latest::latest_to_spec_builder(api_spec) + .unwrap() + .finish(); + let smbios = spec + .smbios_type1_input + .as_ref() + .expect("SMBIOS type 1 input preserved"); + assert_eq!(&smbios_input, smbios); + + let v6_spec = + v6::instance_spec::InstanceSpec::try_from(spec.clone()).unwrap(); + let smbios = + v6_spec.smbios.as_ref().expect("SMBIOS type 1 input preserved"); + assert_eq!(&smbios_input, smbios); + + let v3_spec = + v3::instance_spec::InstanceSpec::try_from(spec.clone()).unwrap(); + let smbios = + v3_spec.smbios.as_ref().expect("SMBIOS type 1 input preserved"); + assert_eq!(&smbios_input, smbios); + + let v2_spec = + v2::instance_spec::InstanceSpec::try_from(spec.clone()).unwrap(); + let smbios = + v2_spec.smbios.as_ref().expect("SMBIOS type 1 input preserved"); + assert_eq!(&smbios_input, smbios); + + let v1_res = v1::instance_spec::InstanceSpec::try_from(spec); + assert!(v1_res.is_err()); + } +} diff --git a/bin/propolis-server/src/lib/spec/api_spec_v3.rs b/bin/propolis-server/src/lib/spec/api_spec_v3.rs index d55cc609b..88c983f77 100644 --- a/bin/propolis-server/src/lib/spec/api_spec_v3.rs +++ b/bin/propolis-server/src/lib/spec/api_spec_v3.rs @@ -125,3 +125,77 @@ pub(crate) fn amend( Ok(()) } + +#[cfg(test)] +mod test { + use propolis_api_types_versions::latest::components::board::{ + Board, Chipset, Cpuid, GuestHypervisorInterface, I440Fx, + }; + use propolis_api_types_versions::latest::{ + self, + components::devices::VirtioSocket, + instance_spec::{self, CpuidVendor, SmbiosType1Input, SpecKey}, + }; + use propolis_api_types_versions::{v1, v2, v3, v6}; + + #[test] + fn vsock_component() { + let mut api_spec = latest::instance_spec::InstanceSpec { + board: Board { + cpus: 4, + memory_mb: 512, + chipset: Chipset::I440Fx(I440Fx { enable_pcie: false }), + guest_hv_interface: GuestHypervisorInterface::Bhyve, + // Providing *any* CPUID settings keeps Propolis from querying + // bhyve for defaults to use instead, which requires .. byhve + // *and* VMM access. Provide a (useless) empty CPUID set so this + // test can run on non-illumos test systems. + cpuid: Some(Cpuid { + entries: vec![], + vendor: CpuidVendor::Amd, + }), + }, + components: Default::default(), + smbios: Some(SmbiosType1Input { + manufacturer: "a4x2".to_string(), + product_name: "913-0000019".to_string(), + serial_number: "2FAKE000".to_string(), + version: 2, + }), + }; + + let vsock_id: SpecKey = SpecKey::Name("vsock-id".to_string()); + let test_vsock: VirtioSocket = VirtioSocket { + guest_cid: 0, + pci_path: instance_spec::PciPath::new(0, 4, 0).unwrap(), + }; + + let vsock_comp = instance_spec::Component::VirtioSocket(test_vsock); + + api_spec.components.insert(vsock_id.clone(), vsock_comp.clone()); + + let spec = + crate::spec::api_spec_latest::latest_to_spec_builder(api_spec) + .unwrap() + .finish(); + assert!(spec.vsock.is_some()); + + let v6_spec = + v6::instance_spec::InstanceSpec::try_from(spec.clone()).unwrap(); + let v6_comp: v6::instance_spec::Component = + vsock_comp.clone().try_into().unwrap(); + assert_eq!(v6_spec.components.get(&vsock_id), Some(&v6_comp)); + + let v3_spec = + v3::instance_spec::InstanceSpec::try_from(spec.clone()).unwrap(); + let v3_comp: v3::instance_spec::Component = + vsock_comp.clone().try_into().unwrap(); + assert_eq!(v3_spec.components.get(&vsock_id), Some(&v3_comp)); + + let v2_res = v2::instance_spec::InstanceSpec::try_from(spec.clone()); + assert!(v2_res.is_err()); + + let v1_res = v1::instance_spec::InstanceSpec::try_from(spec); + assert!(v1_res.is_err()); + } +} diff --git a/bin/propolis-server/src/lib/spec/builder.rs b/bin/propolis-server/src/lib/spec/builder.rs index 78560da8e..e3290369e 100644 --- a/bin/propolis-server/src/lib/spec/builder.rs +++ b/bin/propolis-server/src/lib/spec/builder.rs @@ -11,7 +11,7 @@ use propolis_api_types::instance_spec::{ board::Board as InstanceSpecBoard, devices::{PciPciBridge, SerialPortNumber}, }, - PciPath, SpecKey, + PciPath, SmbiosType1Input, SpecKey, }; use thiserror::Error; @@ -391,6 +391,11 @@ impl SpecBuilder { Ok(self) } + /// Sets the SMBIOS type 1 table contents to expose to the guest. + pub fn set_smbios_type1_input(&mut self, input: SmbiosType1Input) { + self.spec.smbios_type1_input = Some(input); + } + /// Yields the completed spec, consuming the builder. pub fn finish(self) -> super::Spec { self.spec diff --git a/crates/propolis-api-types-versions/src/add_vsock/instance_spec.rs b/crates/propolis-api-types-versions/src/add_vsock/instance_spec.rs index 5072232bf..aa3025a33 100644 --- a/crates/propolis-api-types-versions/src/add_vsock/instance_spec.rs +++ b/crates/propolis-api-types-versions/src/add_vsock/instance_spec.rs @@ -18,7 +18,7 @@ use crate::v2::instance_spec::SmbiosType1Input; pub use super::components::devices::VirtioSocket; -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde( deny_unknown_fields, tag = "type", diff --git a/crates/propolis-api-types-versions/src/initial/components/backends.rs b/crates/propolis-api-types-versions/src/initial/components/backends.rs index 0a711faab..dfb53a36e 100644 --- a/crates/propolis-api-types-versions/src/initial/components/backends.rs +++ b/crates/propolis-api-types-versions/src/initial/components/backends.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; /// A Crucible storage backend. -#[derive(Clone, Deserialize, Serialize, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct CrucibleStorageBackend { /// A serialized `[crucible_client_types::VolumeConstructionRequest]`. This @@ -40,7 +40,7 @@ impl std::fmt::Debug for CrucibleStorageBackend { } /// A storage backend backed by a file in the host system's file system. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct FileStorageBackend { /// A path to a file that backs a disk. @@ -58,7 +58,7 @@ pub struct FileStorageBackend { /// A storage backend for a disk whose initial contents are given explicitly /// by the specification. -#[derive(Clone, Deserialize, Serialize, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct BlobStorageBackend { /// The disk's initial contents, encoded as a base64 string. @@ -78,7 +78,7 @@ impl std::fmt::Debug for BlobStorageBackend { } /// A network backend associated with a virtio-net (viona) VNIC on the host. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct VirtioNetworkBackend { /// The name of the viona VNIC to use as a backend. @@ -86,7 +86,7 @@ pub struct VirtioNetworkBackend { } /// A network backend associated with a DLPI VNIC on the host. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct DlpiNetworkBackend { /// The name of the VNIC to use as a backend. diff --git a/crates/propolis-api-types-versions/src/initial/components/devices.rs b/crates/propolis-api-types-versions/src/initial/components/devices.rs index 4c2e3f2e2..e01c2806c 100644 --- a/crates/propolis-api-types-versions/src/initial/components/devices.rs +++ b/crates/propolis-api-types-versions/src/initial/components/devices.rs @@ -10,7 +10,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; /// A disk that presents a virtio-block interface to the guest. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct VirtioDisk { /// The name of the disk's backend component. @@ -21,7 +21,7 @@ pub struct VirtioDisk { } /// A disk that presents an NVMe interface to the guest. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct NvmeDisk { /// The name of the disk's backend component. @@ -36,7 +36,7 @@ pub struct NvmeDisk { } /// A network card that presents a virtio-net interface to the guest. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct VirtioNic { /// The name of the device's backend. @@ -111,7 +111,9 @@ pub struct QemuPvpanic { /// Settings supplied to the guest's firmware image that specify the order in /// which it should consider its options when selecting a device to try to boot /// from. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, Default)] +#[derive( + Clone, Deserialize, Serialize, Debug, JsonSchema, Default, PartialEq, +)] #[serde(deny_unknown_fields)] pub struct BootSettings { /// An ordered list of components to attempt to boot from. @@ -119,7 +121,7 @@ pub struct BootSettings { } /// An entry in the boot order stored in a [`BootSettings`] component. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] pub struct BootOrderEntry { /// The ID of another component in the spec that Propolis should try to /// boot from. @@ -136,7 +138,7 @@ pub struct BootOrderEntry { /// /// This is only supported by Propolis servers compiled with the `falcon` /// feature. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct SoftNpuPciPort { /// The PCI path at which to attach the guest to this port. @@ -147,7 +149,7 @@ pub struct SoftNpuPciPort { /// /// This is only supported by Propolis servers compiled with the `falcon` /// feature. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct SoftNpuPort { /// The data link name for this port. @@ -162,7 +164,7 @@ pub struct SoftNpuPort { /// /// This is only supported by Propolis servers compiled with the `falcon` /// feature. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct SoftNpuP9 { /// The PCI path at which to attach the guest to this port. @@ -173,7 +175,7 @@ pub struct SoftNpuP9 { /// /// This is only supported by Propolis servers compiled with the `falcon` /// feature. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct P9fs { /// The host source path to mount into the guest. @@ -195,7 +197,7 @@ pub struct P9fs { /// /// This is only supported by Propolis servers compiled with the /// `failure-injection` feature. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct MigrationFailureInjector { /// The number of times this device should fail requests to export state. diff --git a/crates/propolis-api-types-versions/src/initial/instance_spec.rs b/crates/propolis-api-types-versions/src/initial/instance_spec.rs index cbfe6087e..e2cbbf818 100644 --- a/crates/propolis-api-types-versions/src/initial/instance_spec.rs +++ b/crates/propolis-api-types-versions/src/initial/instance_spec.rs @@ -113,7 +113,7 @@ impl JsonSchema for SpecKey { } } -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde( deny_unknown_fields, tag = "type", diff --git a/crates/propolis-api-types-versions/src/nvme_write_cache/components/devices.rs b/crates/propolis-api-types-versions/src/nvme_write_cache/components/devices.rs index cfe04ed2a..2e43f8452 100644 --- a/crates/propolis-api-types-versions/src/nvme_write_cache/components/devices.rs +++ b/crates/propolis-api-types-versions/src/nvme_write_cache/components/devices.rs @@ -9,7 +9,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; /// A disk that presents an NVMe interface to the guest. -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct NvmeDisk { /// The name of the disk's backend component. diff --git a/crates/propolis-api-types-versions/src/nvme_write_cache/instance_spec.rs b/crates/propolis-api-types-versions/src/nvme_write_cache/instance_spec.rs index cfe8d491e..9255a4fd7 100644 --- a/crates/propolis-api-types-versions/src/nvme_write_cache/instance_spec.rs +++ b/crates/propolis-api-types-versions/src/nvme_write_cache/instance_spec.rs @@ -19,7 +19,7 @@ use crate::v3::instance_spec::Component as V3Component; pub use super::components::devices::NvmeDisk; -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde( deny_unknown_fields, tag = "type", diff --git a/crates/propolis-api-types-versions/src/programmable_smbios/instance_spec.rs b/crates/propolis-api-types-versions/src/programmable_smbios/instance_spec.rs index afa73a05f..eadd94f28 100644 --- a/crates/propolis-api-types-versions/src/programmable_smbios/instance_spec.rs +++ b/crates/propolis-api-types-versions/src/programmable_smbios/instance_spec.rs @@ -14,7 +14,7 @@ use crate::v1::components::board; use crate::v1::instance::{InstanceProperties, InstanceState}; use crate::v1::instance_spec::{Component, SpecKey}; -#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Serialize, Debug, JsonSchema, PartialEq)] #[serde(deny_unknown_fields)] pub struct SmbiosType1Input { pub manufacturer: String,