Skip to content

Commit 2af1cca

Browse files
gjcolomboiximeow
authored andcommitted
instance minimum CPU platforms
RFD 505 proposes that instances should be able to set a "minimum hardware platform" or "minimum CPU platform" that allows uers to constrain an instance to run on sleds that have a specific set of CPU features available. Previously, actually-available CPU information was plumbed from sleds to Nexus. This actually adds a `min_cpu_platform` setting for instance creation and uses it to drive selection of guest CPUID leaves. As-is, this moves VMs on Gimlets away from the byhve-default CPUID leaves (which are effectively "host CPUID information, but features that are not or cannot be virtualized are masked out"), instead using the specific CPUID information set out in RFD 505. There is no provision for Turin yet, which instead gets CPUID leaves that look like Milan. Adding a set of CPUID information to advertise for an `amd_turin` CPU platform, from here, is fairly straightforward. This does not have a mechanism to enforce specific CPU platform use or disuse, either in a silo or rack-wide. One could imagine a simple system oriented around "this silo is permitted to specify these minimum CPU platforms", but that leaves uncomfortable issues like: if a silo A permits only Milan, and silo B permits Milan and Turin, all Milan CPUs are allocated already, and someone is attemting to create a new Milan-based VM in silo A, should this succeed using Turin CPUs potentially starving silo B?
1 parent 4130d30 commit 2af1cca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1232
-60
lines changed

common/src/api/external/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,10 @@ pub struct Instance {
11941194

11951195
#[serde(flatten)]
11961196
pub auto_restart_status: InstanceAutoRestartStatus,
1197+
1198+
/// The minimum required CPU platform for this instance. If this is `null`,
1199+
/// the instance requires no particular CPU platform.
1200+
pub min_cpu_platform: Option<InstanceMinimumCpuPlatform>,
11971201
}
11981202

11991203
/// Status of control-plane driven automatic failure recovery for this instance.
@@ -1258,6 +1262,46 @@ pub enum InstanceAutoRestartPolicy {
12581262
BestEffort,
12591263
}
12601264

1265+
/// A minimum required CPU platform for an instance.
1266+
///
1267+
/// When an instance specifies a minimum required CPU platform:
1268+
///
1269+
/// - The system may expose (to the VM) new CPU features that are only present
1270+
/// on that platform (or on newer platforms of the same lineage that also
1271+
/// support those features).
1272+
/// - The instance must run on hosts that have CPUs that support all the
1273+
/// features of the supplied minimum platform.
1274+
///
1275+
/// That is, the instance is restricted to hosts that have the specified minimum
1276+
/// host CPU type (or a more advanced, but still compatible, CPU), but in
1277+
/// exchange the CPU features exposed by the minimum platform are available for
1278+
/// the guest to use. Note that this may prevent an instance from starting (if
1279+
/// the hosts it requires are full but there is capacity on other incompatible
1280+
/// hosts).
1281+
///
1282+
/// If an instance does not specify a minimum required CPU platform, then when
1283+
/// it starts, the control plane selects a host for the instance and then
1284+
/// supplies the guest with the "minimum" CPU platform supported by that host.
1285+
/// This maximizes the number of hosts that can run the VM if it later needs to
1286+
/// migrate to another host.
1287+
///
1288+
/// In all cases, the CPU features presented by a given CPU platform are a
1289+
/// subset of what the corresponding hardware may actually support; features
1290+
/// which cannot be used from a virtual environment or do not have full
1291+
/// hypervisor support may be masked off. See RFD 314 for specific CPU features
1292+
/// in a CPU platform.
1293+
#[derive(
1294+
Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, Eq, PartialEq,
1295+
)]
1296+
#[serde(rename_all = "snake_case")]
1297+
pub enum InstanceMinimumCpuPlatform {
1298+
/// An AMD Milan-like CPU platform.
1299+
AmdMilan,
1300+
1301+
/// An AMD Turin-like CPU platform.
1302+
AmdTurin,
1303+
}
1304+
12611305
// AFFINITY GROUPS
12621306

12631307
/// Affinity policy used to describe "what to do when a request cannot be satisfied"

dev-tools/omdb/src/bin/omdb/db.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4760,6 +4760,7 @@ async fn cmd_db_instance_info(
47604760
propolis_ip: _,
47614761
propolis_port: _,
47624762
instance_id: _,
4763+
cpu_platform: _,
47634764
time_created,
47644765
time_deleted,
47654766
runtime:
@@ -7356,6 +7357,7 @@ fn prettyprint_vmm(
73567357
const INSTANCE_ID: &'static str = "instance ID";
73577358
const SLED_ID: &'static str = "sled ID";
73587359
const SLED_SERIAL: &'static str = "sled serial";
7360+
const CPU_PLATFORM: &'static str = "CPU platform";
73597361
const ADDRESS: &'static str = "propolis address";
73607362
const STATE: &'static str = "state";
73617363
const WIDTH: usize = const_max_len(&[
@@ -7366,6 +7368,7 @@ fn prettyprint_vmm(
73667368
INSTANCE_ID,
73677369
SLED_ID,
73687370
SLED_SERIAL,
7371+
CPU_PLATFORM,
73697372
STATE,
73707373
ADDRESS,
73717374
]);
@@ -7379,6 +7382,7 @@ fn prettyprint_vmm(
73797382
sled_id,
73807383
propolis_ip,
73817384
propolis_port,
7385+
cpu_platform,
73827386
runtime: db::model::VmmRuntimeState { state, r#gen, time_state_updated },
73837387
} = vmm;
73847388

@@ -7405,6 +7409,7 @@ fn prettyprint_vmm(
74057409
if let Some(serial) = sled_serial {
74067410
println!("{indent}{SLED_SERIAL:>width$}: {serial}");
74077411
}
7412+
println!("{indent}{CPU_PLATFORM:>width$}: {cpu_platform}");
74087413
}
74097414

74107415
async fn cmd_db_vmm_list(
@@ -7480,6 +7485,7 @@ async fn cmd_db_vmm_list(
74807485
sled_id,
74817486
propolis_ip: _,
74827487
propolis_port: _,
7488+
cpu_platform: _,
74837489
runtime:
74847490
db::model::VmmRuntimeState {
74857491
state,

end-to-end-tests/src/instance_launch.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ async fn instance_launch() -> Result<()> {
7979
start: true,
8080
auto_restart_policy: Default::default(),
8181
anti_affinity_groups: Vec::new(),
82+
min_cpu_platform: None,
8283
})
8384
.send()
8485
.await?;

nexus/db-model/src/instance.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use super::InstanceIntendedState as IntendedState;
66
use super::{
77
ByteCount, Disk, ExternalIp, Generation, InstanceAutoRestartPolicy,
8-
InstanceCpuCount, InstanceState, Vmm, VmmState,
8+
InstanceCpuCount, InstanceMinimumCpuPlatform, InstanceState, Vmm, VmmState,
99
};
1010
use crate::collection::DatastoreAttachTargetConfig;
1111
use crate::serde_time_delta::optional_time_delta;
@@ -68,6 +68,9 @@ pub struct Instance {
6868
#[diesel(column_name = boot_disk_id)]
6969
pub boot_disk_id: Option<Uuid>,
7070

71+
/// The instance's minimum required CPU platform.
72+
pub min_cpu_platform: Option<InstanceMinimumCpuPlatform>,
73+
7174
#[diesel(embed)]
7275
pub runtime_state: InstanceRuntimeState,
7376

@@ -139,6 +142,7 @@ impl Instance {
139142
// Intentionally ignore `params.boot_disk_id` here: we can't set
140143
// `boot_disk_id` until the referenced disk is attached.
141144
boot_disk_id: None,
145+
min_cpu_platform: params.min_cpu_platform.map(Into::into),
142146

143147
runtime_state,
144148
intended_state,
@@ -493,4 +497,6 @@ pub struct InstanceUpdate {
493497
pub ncpus: InstanceCpuCount,
494498

495499
pub memory: ByteCount,
500+
501+
pub min_cpu_platform: Option<InstanceMinimumCpuPlatform>,
496502
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
use crate::SledCpuFamily;
6+
7+
use super::impl_enum_type;
8+
use serde::{Deserialize, Serialize};
9+
10+
impl_enum_type!(
11+
InstanceMinimumCpuPlatformEnum:
12+
13+
#[derive(
14+
Copy,
15+
Clone,
16+
Debug,
17+
PartialEq,
18+
AsExpression,
19+
FromSqlRow,
20+
Serialize,
21+
Deserialize
22+
)]
23+
pub enum InstanceMinimumCpuPlatform;
24+
25+
AmdMilan => b"amd_milan"
26+
AmdTurin => b"amd_turin"
27+
);
28+
29+
impl InstanceMinimumCpuPlatform {
30+
/// Returns a slice containing the set of sled CPU families that can
31+
/// accommodate an instance with this minimum CPU platform.
32+
pub fn compatible_sled_cpu_families(&self) -> &'static [SledCpuFamily] {
33+
match self {
34+
// Turin-based sleds have a superset of the features made available
35+
// in a guest's Milan CPU platform
36+
Self::AmdMilan => {
37+
&[SledCpuFamily::AmdMilan, SledCpuFamily::AmdTurin]
38+
}
39+
Self::AmdTurin => &[SledCpuFamily::AmdTurin],
40+
}
41+
}
42+
}
43+
44+
impl From<omicron_common::api::external::InstanceMinimumCpuPlatform>
45+
for InstanceMinimumCpuPlatform
46+
{
47+
fn from(
48+
value: omicron_common::api::external::InstanceMinimumCpuPlatform,
49+
) -> Self {
50+
use omicron_common::api::external::InstanceMinimumCpuPlatform as ApiPlatform;
51+
match value {
52+
ApiPlatform::AmdMilan => Self::AmdMilan,
53+
ApiPlatform::AmdTurin => Self::AmdTurin,
54+
}
55+
}
56+
}
57+
58+
impl From<InstanceMinimumCpuPlatform>
59+
for omicron_common::api::external::InstanceMinimumCpuPlatform
60+
{
61+
fn from(value: InstanceMinimumCpuPlatform) -> Self {
62+
match value {
63+
InstanceMinimumCpuPlatform::AmdMilan => Self::AmdMilan,
64+
InstanceMinimumCpuPlatform::AmdTurin => Self::AmdTurin,
65+
}
66+
}
67+
}

nexus/db-model/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mod instance;
4444
mod instance_auto_restart_policy;
4545
mod instance_cpu_count;
4646
mod instance_intended_state;
47+
mod instance_minimum_cpu_platform;
4748
mod instance_state;
4849
mod internet_gateway;
4950
mod inventory;
@@ -123,6 +124,7 @@ mod utilization;
123124
mod virtual_provisioning_collection;
124125
mod virtual_provisioning_resource;
125126
mod vmm;
127+
mod vmm_cpu_platform;
126128
mod vni;
127129
mod volume;
128130
mod volume_repair;
@@ -181,6 +183,7 @@ pub use instance::*;
181183
pub use instance_auto_restart_policy::*;
182184
pub use instance_cpu_count::*;
183185
pub use instance_intended_state::*;
186+
pub use instance_minimum_cpu_platform::*;
184187
pub use instance_state::*;
185188
pub use internet_gateway::*;
186189
pub use inventory::*;
@@ -248,6 +251,7 @@ pub use v2p_mapping::*;
248251
pub use virtual_provisioning_collection::*;
249252
pub use virtual_provisioning_resource::*;
250253
pub use vmm::*;
254+
pub use vmm_cpu_platform::*;
251255
pub use vmm_state::*;
252256
pub use vni::*;
253257
pub use volume::*;

nexus/db-model/src/schema_versions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
1616
///
1717
/// This must be updated when you change the database schema. Refer to
1818
/// schema/crdb/README.adoc in the root of this repository for details.
19-
pub const SCHEMA_VERSION: Version = Version::new(174, 0, 0);
19+
pub const SCHEMA_VERSION: Version = Version::new(175, 0, 0);
2020

2121
/// List of all past database schema versions, in *reverse* order
2222
///
@@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
2828
// | leaving the first copy as an example for the next person.
2929
// v
3030
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
31+
KnownVersion::new(175, "add-instance-minimum-cpu-platform"),
3132
KnownVersion::new(174, "sled-cpu-family"),
3233
KnownVersion::new(173, "inv-internal-dns"),
3334
KnownVersion::new(172, "add-zones-with-mupdate-override"),

nexus/db-model/src/sled.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,13 @@ impl SledUpdate {
340340
#[derive(Clone, Debug)]
341341
pub struct SledReservationConstraints {
342342
must_select_from: Vec<Uuid>,
343+
cpu_families: Vec<SledCpuFamily>,
343344
}
344345

345346
impl SledReservationConstraints {
346347
/// Creates a constraint set with no constraints in it.
347348
pub fn none() -> Self {
348-
Self { must_select_from: Vec::new() }
349+
Self { must_select_from: Vec::new(), cpu_families: Vec::new() }
349350
}
350351

351352
/// If the constraints include a set of sleds that the caller must select
@@ -359,6 +360,19 @@ impl SledReservationConstraints {
359360
Some(&self.must_select_from)
360361
}
361362
}
363+
364+
/// If the constraints include a list of acceptable sled CPU families,
365+
/// returns `Some` and a slice containing the members of that set.
366+
///
367+
/// If no "must select a sled with one of these CPUs" constraint exists,
368+
/// returns None.
369+
pub fn cpu_families(&self) -> Option<&[SledCpuFamily]> {
370+
if self.cpu_families.is_empty() {
371+
None
372+
} else {
373+
Some(&self.cpu_families)
374+
}
375+
}
362376
}
363377

364378
#[derive(Debug)]
@@ -381,6 +395,11 @@ impl SledReservationConstraintBuilder {
381395
self
382396
}
383397

398+
pub fn cpu_families(mut self, families: &[SledCpuFamily]) -> Self {
399+
self.constraints.cpu_families.extend(families);
400+
self
401+
}
402+
384403
/// Builds a set of constraints from this builder's current state.
385404
pub fn build(self) -> SledReservationConstraints {
386405
self.constraints

nexus/db-model/src/sled_cpu_family.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ impl_enum_type!(
2525
AmdTurin => b"amd_turin"
2626
);
2727

28+
impl SledCpuFamily {
29+
/// Yields the minimum compatible instance CPU platform that can run on this
30+
/// sled.
31+
///
32+
/// Each instance CPU platform has a set `C` of sled CPU families that can
33+
/// host it. The "minimum compatible platform" is the instance CPU platform
34+
/// for which (a) `self` is in `C`, and (b) `C` is of maximum cardinality.
35+
/// That is: the minimum compatible platform is chosen so that a VMM that
36+
/// uses it can run on sleds of this family and as many other families as
37+
/// possible.
38+
pub fn minimum_compatible_platform(&self) -> crate::VmmCpuPlatform {
39+
match self {
40+
Self::Unknown => crate::VmmCpuPlatform::SledDefault,
41+
Self::AmdMilan => crate::VmmCpuPlatform::AmdMilan,
42+
Self::AmdTurin => crate::VmmCpuPlatform::AmdMilan,
43+
}
44+
}
45+
}
46+
2847
impl From<nexus_types::internal_api::params::SledCpuFamily> for SledCpuFamily {
2948
fn from(value: nexus_types::internal_api::params::SledCpuFamily) -> Self {
3049
use nexus_types::internal_api::params::SledCpuFamily as InputFamily;

nexus/db-model/src/vmm.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
//! sled agent or that sled agent will never update (like the sled ID).
1414
1515
use super::{Generation, VmmState};
16-
use crate::SqlU16;
16+
use crate::{SqlU16, VmmCpuPlatform};
1717
use chrono::{DateTime, Utc};
1818
use nexus_db_schema::schema::vmm;
1919
use omicron_uuid_kinds::{GenericUuid, InstanceUuid, PropolisUuid, SledUuid};
@@ -55,6 +55,11 @@ pub struct Vmm {
5555
/// The socket port on which this VMM is serving the Propolis server API.
5656
pub propolis_port: SqlU16,
5757

58+
/// The CPU platform for this VMM. This may be chosen implicitly by the
59+
/// control plane if this VMM's instance didn't specify a required platform
60+
/// when it was started.
61+
pub cpu_platform: VmmCpuPlatform,
62+
5863
/// Runtime state for the VMM.
5964
#[diesel(embed)]
6065
pub runtime: VmmRuntimeState,
@@ -71,6 +76,7 @@ impl Vmm {
7176
sled_id: SledUuid,
7277
propolis_ip: ipnetwork::IpNetwork,
7378
propolis_port: u16,
79+
cpu_platform: VmmCpuPlatform,
7480
) -> Self {
7581
let now = Utc::now();
7682

@@ -82,6 +88,7 @@ impl Vmm {
8288
sled_id: sled_id.into_untyped_uuid(),
8389
propolis_ip,
8490
propolis_port: SqlU16(propolis_port),
91+
cpu_platform,
8592
runtime: VmmRuntimeState {
8693
state: VmmState::Creating,
8794
time_state_updated: now,

0 commit comments

Comments
 (0)