Skip to content

Commit 3a3e753

Browse files
committed
use newtypes to delineate DeviceName/BackendName in PHD
these newtypes are useful for other kinds of devices, and in propolis-api-types generally, but threading that through is more time than i can spend on this right now
1 parent 22b5a08 commit 3a3e753

File tree

6 files changed

+110
-57
lines changed

6 files changed

+110
-57
lines changed

phd-tests/framework/src/disk/crucible.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ use tracing::{error, info};
2121
use uuid::Uuid;
2222

2323
use super::BlockSize;
24-
use crate::{guest_os::GuestOsKind, server_log_mode::ServerLogMode};
24+
use crate::{
25+
disk::DeviceName, guest_os::GuestOsKind, server_log_mode::ServerLogMode,
26+
};
2527

2628
/// An RAII wrapper around a directory containing Crucible data files. Deletes
2729
/// the directory and its contents when dropped.
@@ -60,8 +62,8 @@ impl Drop for Downstairs {
6062
/// An RAII wrapper around a Crucible disk.
6163
#[derive(Debug)]
6264
pub struct CrucibleDisk {
63-
/// The name of the backend to use in instance specs that include this disk.
64-
backend_name: String,
65+
/// The name to use in instance specs that include this disk.
66+
device_name: DeviceName,
6567

6668
/// The UUID to insert into this disk's `VolumeConstructionRequest`s.
6769
id: Uuid,
@@ -97,7 +99,7 @@ impl CrucibleDisk {
9799
/// `data_dir`.
98100
#[allow(clippy::too_many_arguments)]
99101
pub(crate) fn new(
100-
backend_name: String,
102+
device_name: DeviceName,
101103
min_disk_size_gib: u64,
102104
block_size: BlockSize,
103105
downstairs_binary_path: &impl AsRef<std::ffi::OsStr>,
@@ -251,7 +253,7 @@ impl CrucibleDisk {
251253
}
252254

253255
Ok(Self {
254-
backend_name,
256+
device_name,
255257
id: disk_uuid,
256258
block_size,
257259
blocks_per_extent,
@@ -280,7 +282,11 @@ impl CrucibleDisk {
280282
}
281283

282284
impl super::DiskConfig for CrucibleDisk {
283-
fn backend_spec(&self) -> (String, StorageBackendV0) {
285+
fn device_name(&self) -> &DeviceName {
286+
&self.device_name
287+
}
288+
289+
fn backend_spec(&self) -> StorageBackendV0 {
284290
let gen = self.generation.load(Ordering::Relaxed);
285291
let downstairs_addrs = self
286292
.downstairs_instances
@@ -318,14 +324,11 @@ impl super::DiskConfig for CrucibleDisk {
318324
}),
319325
};
320326

321-
(
322-
self.backend_name.clone(),
323-
StorageBackendV0::Crucible(CrucibleStorageBackend {
324-
request_json: serde_json::to_string(&vcr)
325-
.expect("VolumeConstructionRequest should serialize"),
326-
readonly: false,
327-
}),
328-
)
327+
StorageBackendV0::Crucible(CrucibleStorageBackend {
328+
request_json: serde_json::to_string(&vcr)
329+
.expect("VolumeConstructionRequest should serialize"),
330+
readonly: false,
331+
})
329332
}
330333

331334
fn guest_os(&self) -> Option<GuestOsKind> {

phd-tests/framework/src/disk/file.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use propolis_client::types::{FileStorageBackend, StorageBackendV0};
99
use tracing::{debug, error, warn};
1010
use uuid::Uuid;
1111

12-
use crate::{guest_os::GuestOsKind, zfs::ClonedFile as ZfsClonedFile};
12+
use crate::{
13+
disk::DeviceName, guest_os::GuestOsKind, zfs::ClonedFile as ZfsClonedFile,
14+
};
1315

1416
/// Describes the method used to create the backing file for a file-backed disk.
1517
#[derive(Debug)]
@@ -80,7 +82,7 @@ impl Drop for BackingFile {
8082
#[derive(Debug)]
8183
pub struct FileBackedDisk {
8284
/// The name to use for instance spec backends that refer to this disk.
83-
backend_name: String,
85+
device_name: DeviceName,
8486

8587
/// The backing file for this disk.
8688
file: BackingFile,
@@ -94,7 +96,7 @@ impl FileBackedDisk {
9496
/// Creates a new file-backed disk whose initial contents are copied from
9597
/// the specified artifact on the host file system.
9698
pub(crate) fn new_from_artifact(
97-
backend_name: String,
99+
device_name: DeviceName,
98100
artifact_path: &impl AsRef<Utf8Path>,
99101
data_dir: &impl AsRef<Utf8Path>,
100102
guest_os: Option<GuestOsKind>,
@@ -116,19 +118,20 @@ impl FileBackedDisk {
116118
permissions.set_readonly(false);
117119
disk_file.set_permissions(permissions)?;
118120

119-
Ok(Self { backend_name, file: artifact, guest_os })
121+
Ok(Self { device_name, file: artifact, guest_os })
120122
}
121123
}
122124

123125
impl super::DiskConfig for FileBackedDisk {
124-
fn backend_spec(&self) -> (String, StorageBackendV0) {
125-
(
126-
self.backend_name.clone(),
127-
StorageBackendV0::File(FileStorageBackend {
128-
path: self.file.path().to_string(),
129-
readonly: false,
130-
}),
131-
)
126+
fn device_name(&self) -> &DeviceName {
127+
&self.device_name
128+
}
129+
130+
fn backend_spec(&self) -> StorageBackendV0 {
131+
StorageBackendV0::File(FileStorageBackend {
132+
path: self.file.path().to_string(),
133+
readonly: false,
134+
})
132135
}
133136

134137
fn guest_os(&self) -> Option<GuestOsKind> {

phd-tests/framework/src/disk/in_memory.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
use propolis_client::types::{BlobStorageBackend, StorageBackendV0};
88

99
use super::DiskConfig;
10+
use crate::disk::DeviceName;
1011

1112
/// A disk with an in-memory backend.
1213
#[derive(Debug)]
1314
pub struct InMemoryDisk {
14-
backend_name: String,
15+
device_name: DeviceName,
1516
contents: Vec<u8>,
1617
readonly: bool,
1718
}
@@ -20,28 +21,29 @@ impl InMemoryDisk {
2021
/// Creates an in-memory backend that will present the supplied `contents`
2122
/// to the guest.
2223
pub fn new(
23-
backend_name: String,
24+
device_name: DeviceName,
2425
contents: Vec<u8>,
2526
readonly: bool,
2627
) -> Self {
27-
Self { backend_name, contents, readonly }
28+
Self { device_name, contents, readonly }
2829
}
2930
}
3031

3132
impl DiskConfig for InMemoryDisk {
32-
fn backend_spec(&self) -> (String, StorageBackendV0) {
33+
fn device_name(&self) -> &DeviceName {
34+
&self.device_name
35+
}
36+
37+
fn backend_spec(&self) -> StorageBackendV0 {
3338
let base64 = base64::Engine::encode(
3439
&base64::engine::general_purpose::STANDARD,
3540
self.contents.as_slice(),
3641
);
3742

38-
(
39-
self.backend_name.clone(),
40-
StorageBackendV0::Blob(BlobStorageBackend {
41-
base64,
42-
readonly: self.readonly,
43-
}),
44-
)
43+
StorageBackendV0::Blob(BlobStorageBackend {
44+
base64,
45+
readonly: self.readonly,
46+
})
4547
}
4648

4749
fn guest_os(&self) -> Option<crate::guest_os::GuestOsKind> {

phd-tests/framework/src/disk/mod.rs

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,54 @@ impl BlockSize {
6767
}
6868
}
6969

70+
/// The name for the device implementing a disk. This is the name provided for a
71+
/// disk in constructing a VM spec for PHD tests. The disk by this name likely
72+
/// also has a [`BackendName`] derived from this device name.
73+
///
74+
/// TODO: This exists largely to ensure that PHD matches the same spec
75+
/// construction conventions as `propolis-server` when handling API requests: it
76+
/// is another piece of glue that could reasonably be deleted if/when PHD and
77+
/// sled-agent use the same code to build InstanceEnsureRequest. Until then,
78+
/// carefully match the relationship between names with these newtypes.
79+
///
80+
/// Alternatively, `DeviceName` and `BackendName` could be pulled into
81+
/// `propolis-api-types`.
82+
#[derive(Clone, Debug)]
83+
pub struct DeviceName(String);
84+
85+
impl DeviceName {
86+
pub fn new(name: String) -> Self {
87+
DeviceName(name)
88+
}
89+
90+
pub fn into_backend_name(self) -> BackendName {
91+
// This must match `api_request.rs`' `parse_disk_from_request`.
92+
BackendName(format!("{}-backend", self.0))
93+
}
94+
95+
pub fn into_string(self) -> String {
96+
self.0
97+
}
98+
}
99+
100+
/// The name for a backend implementing storage for a disk. This is derived
101+
/// exclusively from a corresponding [`DeviceName`].
102+
#[derive(Clone, Debug)]
103+
pub struct BackendName(String);
104+
105+
impl BackendName {
106+
pub fn into_string(self) -> String {
107+
self.0
108+
}
109+
}
110+
70111
/// A trait for functions exposed by all disk backends (files, Crucible, etc.).
71112
pub trait DiskConfig: std::fmt::Debug + Send + Sync {
113+
/// Yields the device name for this disk.
114+
fn device_name(&self) -> &DeviceName;
115+
72116
/// Yields the backend spec for this disk's storage backend.
73-
fn backend_spec(&self) -> (String, StorageBackendV0);
117+
fn backend_spec(&self) -> StorageBackendV0;
74118

75119
/// Yields the guest OS kind of the guest image the disk was created from,
76120
/// or `None` if the disk was not created from a guest image.
@@ -182,7 +226,7 @@ impl DiskFactory {
182226
/// by `source`.
183227
pub(crate) async fn create_file_backed_disk<'d>(
184228
&self,
185-
name: String,
229+
name: DeviceName,
186230
source: &DiskSource<'d>,
187231
) -> Result<Arc<FileBackedDisk>, DiskError> {
188232
let artifact_name = match source {
@@ -226,7 +270,7 @@ impl DiskFactory {
226270
/// - block_size: The disk's block size.
227271
pub(crate) async fn create_crucible_disk<'d>(
228272
&self,
229-
name: String,
273+
name: DeviceName,
230274
source: &DiskSource<'d>,
231275
mut min_disk_size_gib: u64,
232276
block_size: BlockSize,
@@ -278,7 +322,7 @@ impl DiskFactory {
278322

279323
pub(crate) async fn create_in_memory_disk<'d>(
280324
&self,
281-
name: String,
325+
name: DeviceName,
282326
source: &DiskSource<'d>,
283327
readonly: bool,
284328
) -> Result<Arc<InMemoryDisk>, DiskError> {

phd-tests/framework/src/test_vm/config.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use propolis_client::{
1616
use uuid::Uuid;
1717

1818
use crate::{
19-
disk::{DiskConfig, DiskSource},
19+
disk::{DeviceName, DiskConfig, DiskSource},
2020
test_vm::spec::VmSpec,
2121
Framework,
2222
};
@@ -268,30 +268,27 @@ impl<'dr> VmConfig<'dr> {
268268
let all_disks = self.disks.iter().zip(disk_handles.iter());
269269
for (req, hdl) in all_disks {
270270
let pci_path = PciPath::new(0, req.pci_device_num, 0).unwrap();
271-
let (device_name, backend_spec) = hdl.backend_spec();
272-
// propolis-server uses a disk's name from the API as its device
273-
// name. It then derives a backend name from the disk name. Match
274-
// that name derivation here so PHD tests match the API as used by
275-
// Nexus.
276-
let backend_name = format!("{}-backend", device_name);
271+
let backend_spec = hdl.backend_spec();
272+
let device_name = hdl.device_name().clone();
273+
let backend_name = device_name.clone().into_backend_name();
277274
let device_spec = match req.interface {
278275
DiskInterface::Virtio => {
279276
StorageDeviceV0::VirtioDisk(VirtioDisk {
280-
backend_name: backend_name.clone(),
277+
backend_name: backend_name.clone().into_string(),
281278
pci_path,
282279
})
283280
}
284281
DiskInterface::Nvme => StorageDeviceV0::NvmeDisk(NvmeDisk {
285-
backend_name: backend_name.clone(),
282+
backend_name: backend_name.clone().into_string(),
286283
pci_path,
287284
}),
288285
};
289286

290287
spec_builder
291288
.add_storage_device(
292-
device_name,
289+
device_name.into_string(),
293290
device_spec,
294-
backend_name,
291+
backend_name.into_string(),
295292
backend_spec,
296293
)
297294
.context("adding storage device to spec")?;
@@ -334,22 +331,24 @@ impl<'dr> VmConfig<'dr> {
334331
}
335332

336333
async fn make_disk<'req>(
337-
name: String,
334+
device_name: String,
338335
framework: &Framework,
339336
req: &DiskRequest<'req>,
340337
) -> anyhow::Result<Arc<dyn DiskConfig>> {
338+
let device_name = DeviceName::new(device_name);
339+
341340
Ok(match req.backend {
342341
DiskBackend::File => framework
343342
.disk_factory
344-
.create_file_backed_disk(name, &req.source)
343+
.create_file_backed_disk(device_name, &req.source)
345344
.await
346345
.with_context(|| {
347346
format!("creating new file-backed disk from {:?}", req.source,)
348347
})? as Arc<dyn DiskConfig>,
349348
DiskBackend::Crucible { min_disk_size_gib, block_size } => framework
350349
.disk_factory
351350
.create_crucible_disk(
352-
name,
351+
device_name,
353352
&req.source,
354353
min_disk_size_gib,
355354
block_size,
@@ -364,7 +363,7 @@ async fn make_disk<'req>(
364363
as Arc<dyn DiskConfig>,
365364
DiskBackend::InMemory { readonly } => framework
366365
.disk_factory
367-
.create_in_memory_disk(name, &req.source, readonly)
366+
.create_in_memory_disk(device_name, &req.source, readonly)
368367
.await
369368
.with_context(|| {
370369
format!("creating new in-memory disk from {:?}", req.source)

phd-tests/framework/src/test_vm/spec.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ impl VmSpec {
4646
continue;
4747
};
4848

49-
let (backend_name, backend_spec) = disk.backend_spec();
49+
let backend_spec = disk.backend_spec();
50+
let backend_name =
51+
disk.device_name().clone().into_backend_name().into_string();
5052
match self
5153
.instance_spec
5254
.backends

0 commit comments

Comments
 (0)