Skip to content

Commit a9ccbbf

Browse files
committed
merge
2 parents f60f53f + 6fa3208 commit a9ccbbf

Some content is hidden

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

65 files changed

+31808
-579
lines changed

.github/buildomat/jobs/deploy.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ E2E_TLS_CERT="/opt/oxide/sled-agent/pkg/initial-tls-cert.pem"
343343
#
344344
pfexec mkdir -p /usr/oxide
345345
pfexec curl -sSfL -o /usr/oxide/oxide \
346-
http://catacomb.eng.oxide.computer:12346/oxide-v0.1.1
346+
http://catacomb.eng.oxide.computer:12346/oxide-v2025112000
347347
pfexec chmod +x /usr/oxide/oxide
348348

349349
curl -sSfL -o debian-11-genericcloud-amd64.raw \

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/src/api/external/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,16 @@ impl Display for ByteCount {
593593
}
594594

595595
// TODO-cleanup This could use the experimental std::num::IntErrorKind.
596-
#[derive(Debug, Eq, thiserror::Error, Ord, PartialEq, PartialOrd)]
596+
#[derive(
597+
Debug,
598+
Eq,
599+
thiserror::Error,
600+
Ord,
601+
PartialEq,
602+
PartialOrd,
603+
Serialize,
604+
Deserialize,
605+
)]
597606
pub enum ByteCountRangeError {
598607
#[error("value is too small for a byte count")]
599608
TooSmall,
@@ -1424,7 +1433,8 @@ impl SimpleIdentityOrName for AntiAffinityGroupMember {
14241433
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
14251434
#[serde(rename_all = "snake_case")]
14261435
pub enum DiskType {
1427-
Crucible,
1436+
Distributed,
1437+
Local,
14281438
}
14291439

14301440
/// View of a Disk

dev-tools/dropshot-apis/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ fn all_apis() -> anyhow::Result<ManagedApis> {
315315
let apis = ManagedApis::new(apis)
316316
.context("error creating ManagedApis")?
317317
.with_validation(validate);
318+
318319
Ok(apis)
319320
}
320321

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

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ use nexus_db_queries::db::datastore::CrucibleTargets;
120120
use nexus_db_queries::db::datastore::Disk;
121121
use nexus_db_queries::db::datastore::InstanceAndActiveVmm;
122122
use nexus_db_queries::db::datastore::InstanceStateComputer;
123+
use nexus_db_queries::db::datastore::LocalStorageDisk;
123124
use nexus_db_queries::db::datastore::SQL_BATCH_SIZE;
124125
use nexus_db_queries::db::datastore::VolumeCookedResult;
125126
use nexus_db_queries::db::datastore::read_only_resources_associated_with_volume;
@@ -152,6 +153,7 @@ use omicron_common::api::external::MacAddr;
152153
use omicron_uuid_kinds::CollectionUuid;
153154
use omicron_uuid_kinds::DatasetUuid;
154155
use omicron_uuid_kinds::DownstairsRegionUuid;
156+
use omicron_uuid_kinds::ExternalZpoolUuid;
155157
use omicron_uuid_kinds::GenericUuid;
156158
use omicron_uuid_kinds::InstanceUuid;
157159
use omicron_uuid_kinds::ParseError;
@@ -2225,8 +2227,7 @@ async fn crucible_disk_info(
22252227
}
22262228
}
22272229
} else {
2228-
// If the disk is not attached to anything, just print empty
2229-
// fields.
2230+
// If the disk is not attached to anything, just print empty fields.
22302231
UpstairsRow {
22312232
host_serial: "-".to_string(),
22322233
disk_name,
@@ -2286,6 +2287,141 @@ async fn crucible_disk_info(
22862287
Ok(())
22872288
}
22882289

2290+
async fn local_storage_disk_info(
2291+
opctx: &OpContext,
2292+
datastore: &DataStore,
2293+
disk: LocalStorageDisk,
2294+
) -> Result<(), anyhow::Error> {
2295+
#[derive(Tabled)]
2296+
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
2297+
struct GenericRow {
2298+
host_serial: String,
2299+
disk_name: String,
2300+
instance_name: String,
2301+
propolis_zone: String,
2302+
disk_state: String,
2303+
}
2304+
2305+
let conn = datastore.pool_connection_for_tests().await?;
2306+
2307+
let disk_name = disk.name().to_string();
2308+
let disk_state = disk.runtime().disk_state.to_string();
2309+
2310+
let row = if let Some(instance_uuid) = disk.runtime().attach_instance_id {
2311+
// Get the instance this disk is attached to
2312+
use nexus_db_schema::schema::instance::dsl as instance_dsl;
2313+
use nexus_db_schema::schema::vmm::dsl as vmm_dsl;
2314+
let instances: Vec<InstanceAndActiveVmm> = instance_dsl::instance
2315+
.filter(instance_dsl::id.eq(instance_uuid))
2316+
.left_join(
2317+
vmm_dsl::vmm.on(vmm_dsl::id
2318+
.nullable()
2319+
.eq(instance_dsl::active_propolis_id)
2320+
.and(vmm_dsl::time_deleted.is_null())),
2321+
)
2322+
.limit(1)
2323+
.select((Instance::as_select(), Option::<Vmm>::as_select()))
2324+
.load_async(&*conn)
2325+
.await
2326+
.context("loading requested instance")?
2327+
.into_iter()
2328+
.map(|i: (Instance, Option<Vmm>)| i.into())
2329+
.collect();
2330+
2331+
let Some(instance) = instances.into_iter().next() else {
2332+
bail!("no instance: {} found", instance_uuid);
2333+
};
2334+
2335+
let instance_name = instance.instance().name().to_string();
2336+
2337+
if instance.vmm().is_some() {
2338+
let propolis_id =
2339+
instance.instance().runtime().propolis_id.unwrap();
2340+
let my_sled_id = instance.sled_id().unwrap();
2341+
2342+
let (_, my_sled) = LookupPath::new(opctx, datastore)
2343+
.sled_id(my_sled_id)
2344+
.fetch()
2345+
.await
2346+
.context("failed to look up sled")?;
2347+
2348+
GenericRow {
2349+
host_serial: my_sled.serial_number().to_string(),
2350+
disk_name,
2351+
instance_name,
2352+
propolis_zone: format!("oxz_propolis-server_{}", propolis_id),
2353+
disk_state,
2354+
}
2355+
} else {
2356+
GenericRow {
2357+
host_serial: NOT_ON_SLED_MSG.to_string(),
2358+
disk_name,
2359+
instance_name,
2360+
propolis_zone: NO_ACTIVE_PROPOLIS_MSG.to_string(),
2361+
disk_state,
2362+
}
2363+
}
2364+
} else {
2365+
// If the disk is not attached to anything, just print empty fields.
2366+
GenericRow {
2367+
host_serial: "-".to_string(),
2368+
disk_name,
2369+
instance_name: "-".to_string(),
2370+
propolis_zone: "-".to_string(),
2371+
disk_state,
2372+
}
2373+
};
2374+
2375+
let table = tabled::Table::new(vec![row])
2376+
.with(tabled::settings::Style::empty())
2377+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
2378+
.to_string();
2379+
2380+
println!("{}", table);
2381+
2382+
#[derive(Tabled)]
2383+
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
2384+
struct Row {
2385+
disk_name: String,
2386+
2387+
time_created: DateTime<Utc>,
2388+
#[tabled(display_with = "display_option_blank")]
2389+
time_deleted: Option<DateTime<Utc>>,
2390+
2391+
dataset_id: DatasetUuid,
2392+
pool_id: ExternalZpoolUuid,
2393+
sled_id: SledUuid,
2394+
2395+
dataset_size: u64,
2396+
}
2397+
2398+
if let Some(allocation) = &disk.local_storage_dataset_allocation {
2399+
let rows = vec![Row {
2400+
disk_name: disk.name().to_string(),
2401+
2402+
time_created: allocation.time_created,
2403+
time_deleted: allocation.time_deleted,
2404+
2405+
dataset_id: allocation.local_storage_dataset_id(),
2406+
pool_id: allocation.pool_id(),
2407+
sled_id: allocation.sled_id(),
2408+
2409+
dataset_size: allocation.dataset_size.to_bytes(),
2410+
}];
2411+
2412+
let table = tabled::Table::new(rows)
2413+
.with(tabled::settings::Style::empty())
2414+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
2415+
.to_string();
2416+
2417+
println!("{}", table);
2418+
} else {
2419+
println!("no allocation yet");
2420+
}
2421+
2422+
Ok(())
2423+
}
2424+
22892425
/// Run `omdb db disk info <UUID>`.
22902426
async fn cmd_db_disk_info(
22912427
opctx: &OpContext,
@@ -2296,6 +2432,9 @@ async fn cmd_db_disk_info(
22962432
Disk::Crucible(disk) => {
22972433
crucible_disk_info(opctx, datastore, disk).await
22982434
}
2435+
Disk::LocalStorage(disk) => {
2436+
local_storage_disk_info(opctx, datastore, disk).await
2437+
}
22992438
}
23002439
}
23012440

end-to-end-tests/src/bin/bootstrap.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use end_to_end_tests::helpers::{
66
use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition};
77
use oxide_client::types::{
88
ByteCount, DeviceAccessTokenRequest, DeviceAuthRequest, DeviceAuthVerify,
9-
DiskCreate, DiskSource, IpPoolCreate, IpPoolLinkSilo, IpPoolType,
10-
IpVersion, NameOrId, SiloQuotasUpdate,
9+
DiskBackend, DiskCreate, DiskSource, IpPoolCreate, IpPoolLinkSilo,
10+
IpPoolType, IpVersion, NameOrId, SiloQuotasUpdate,
1111
};
1212
use oxide_client::{
1313
ClientConsoleAuthExt, ClientDisksExt, ClientProjectsExt,
@@ -98,9 +98,9 @@ async fn run_test() -> Result<()> {
9898
.body(DiskCreate {
9999
name: disk_name.clone(),
100100
description: String::new(),
101-
disk_source: DiskSource::Blank {
101+
disk_backend: DiskBackend::Distributed(DiskSource::Blank {
102102
block_size: 512.try_into().unwrap(),
103-
},
103+
}),
104104
size: ByteCount(1024 * 1024 * 1024),
105105
})
106106
.send()

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use anyhow::{Context as _, Result, ensure};
55
use async_trait::async_trait;
66
use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition};
77
use oxide_client::types::{
8-
ByteCount, DiskCreate, DiskSource, ExternalIp, ExternalIpCreate,
9-
InstanceCpuCount, InstanceCreate, InstanceDiskAttachment,
8+
ByteCount, DiskBackend, DiskCreate, DiskSource, ExternalIp,
9+
ExternalIpCreate, InstanceCpuCount, InstanceCreate, InstanceDiskAttachment,
1010
InstanceNetworkInterfaceAttachment, InstanceState, SshKeyCreate,
1111
};
1212
use oxide_client::{ClientCurrentUserExt, ClientDisksExt, ClientInstancesExt};
@@ -45,9 +45,9 @@ async fn instance_launch() -> Result<()> {
4545
.body(DiskCreate {
4646
name: disk_name.clone(),
4747
description: String::new(),
48-
disk_source: DiskSource::Image {
48+
disk_backend: DiskBackend::Distributed(DiskSource::Image {
4949
image_id: ctx.get_silo_image_id("debian11").await?,
50-
},
50+
}),
5151
size: ByteCount(2048 * 1024 * 1024),
5252
})
5353
.send()

nexus/db-model/src/disk.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ impl_enum_type!(
2626

2727
// Enum values
2828
Crucible => b"crucible"
29+
LocalStorage => b"local_storage"
2930
);
3031

3132
/// A Disk, where how the blocks are stored depend on the disk_type.
@@ -75,7 +76,8 @@ pub struct Disk {
7576
/// (where rows are matched based on the disk_id field in that table) and
7677
/// combined into a higher level `datastore::Disk` enum.
7778
///
78-
/// For `Crucible` disks, see the DiskTypeCrucible model.
79+
/// For `Crucible` disks, see the DiskTypeCrucible model. For `LocalStorage`
80+
/// disks, see the DiskTypeLocalStorage model.
7981
pub disk_type: DiskType,
8082
}
8183

nexus/db-model/src/disk_type_crucible.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,16 @@ impl DiskTypeCrucible {
4343
pub fn new(
4444
disk_id: Uuid,
4545
volume_id: VolumeUuid,
46-
params: &params::DiskCreate,
46+
disk_source: &params::DiskSource,
4747
) -> Self {
48-
let create_snapshot_id = match params.disk_source {
49-
params::DiskSource::Snapshot { snapshot_id } => Some(snapshot_id),
48+
let create_snapshot_id = match disk_source {
49+
params::DiskSource::Snapshot { snapshot_id } => Some(*snapshot_id),
5050
_ => None,
5151
};
5252

5353
// XXX further enum here for different image types?
54-
let create_image_id = match params.disk_source {
55-
params::DiskSource::Image { image_id } => Some(image_id),
54+
let create_image_id = match disk_source {
55+
params::DiskSource::Image { image_id } => Some(*image_id),
5656
_ => None,
5757
};
5858

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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::ByteCount;
6+
use crate::typed_uuid::DbTypedUuid;
7+
use nexus_db_schema::schema::disk_type_local_storage;
8+
use omicron_common::api::external;
9+
use omicron_uuid_kinds::DatasetKind;
10+
use omicron_uuid_kinds::DatasetUuid;
11+
use serde::{Deserialize, Serialize};
12+
use uuid::Uuid;
13+
14+
/// A Disk can be backed using a zvol slice from the local storage dataset
15+
/// present on each zpool of a sled.
16+
#[derive(
17+
Queryable, Insertable, Clone, Debug, Selectable, Serialize, Deserialize,
18+
)]
19+
#[diesel(table_name = disk_type_local_storage)]
20+
pub struct DiskTypeLocalStorage {
21+
disk_id: Uuid,
22+
23+
/// For zvols inside a parent dataset, there's an overhead that must be
24+
/// accounted for when setting a quota and reservation on that parent
25+
/// dataset. Record at model creation time how much overhead is required for
26+
/// the parent `local_storage` dataset slice in order to fit the child
27+
/// volume.
28+
required_dataset_overhead: ByteCount,
29+
30+
local_storage_dataset_allocation_id: Option<DbTypedUuid<DatasetKind>>,
31+
}
32+
33+
impl DiskTypeLocalStorage {
34+
/// Creates a new `DiskTypeLocalStorage`. Returns Err if the computed
35+
/// required dataset overhead does not fit in a `ByteCount`.
36+
pub fn new(
37+
disk_id: Uuid,
38+
size: external::ByteCount,
39+
) -> Result<DiskTypeLocalStorage, external::ByteCountRangeError> {
40+
// For zvols, there's an overhead that must be accounted for, and it
41+
// empirically seems to be about 65M per 1G for volblocksize=4096.
42+
// Multiple the disk size by something a little over this value.
43+
44+
let one_gb = external::ByteCount::from_gibibytes_u32(1).to_bytes();
45+
let gbs = size.to_bytes() / one_gb;
46+
let overhead: u64 =
47+
external::ByteCount::from_mebibytes_u32(70).to_bytes() * gbs;
48+
49+
// Don't unwrap this - the size of this disk is a parameter set by an
50+
// API call, and we don't want to panic on out of range input.
51+
let required_dataset_overhead =
52+
external::ByteCount::try_from(overhead)?;
53+
54+
Ok(DiskTypeLocalStorage {
55+
disk_id,
56+
required_dataset_overhead: required_dataset_overhead.into(),
57+
local_storage_dataset_allocation_id: None,
58+
})
59+
}
60+
61+
pub fn required_dataset_overhead(&self) -> external::ByteCount {
62+
self.required_dataset_overhead.into()
63+
}
64+
65+
pub fn local_storage_dataset_allocation_id(&self) -> Option<DatasetUuid> {
66+
self.local_storage_dataset_allocation_id.map(Into::into)
67+
}
68+
}

0 commit comments

Comments
 (0)