From a88cae977148d1a56540241e4a8b2c7b9d3ac4ce Mon Sep 17 00:00:00 2001
From: Greg Colombo <greg@oxidecomputer.com>
Date: Mon, 24 Feb 2025 20:56:28 +0000
Subject: [PATCH 1/4] phd: smoke test VCR replacement

Add a smoke test for Crucible VCR replacement:

- Add a `CrucibleDisk` function to get a disk's current VCR.
- Add a `TestVm` framework to send a VCR replacement request.
- Fix a couple of bugs in Crucible disk setup:
  - The `id` field in the VCR's `CrucibleOpts` needs to be the same in
    the old and new VCRs, so use the disk ID for it instead of
    calling `Uuid::new_v4()` every time the VCR is generated.
  - When using a `Blank` disk source, properly account for the fact
    that the disk source size is given in bytes, not gibibytes.

Also add a couple of bells and whistles to allow this test to be
transformed into a test of VCR replacement during VM startup:

- Make PHD's `VmSpec` type a public type and amend `Framework` to allow
  tests to create a VM from a spec. This gives tests a way to access
  a config's Crucible disks before actually launching a VM (and sending
  an instance spec to Propolis).
- Reorganize the `CrucibleDisk` types to wrap the disk innards in a
  `Mutex`, allowing them to be mutated through the `Arc` references that
  tests get. This will eventually be used to allow tests to override the
  downstairs addresses in a disk's VCRs before launching a VM that uses
  that disk, which will be used to test #841. In the meantime, use the
  mutex to protect the VCR generation number, which no longer needs to
  be an `AtomicU64`.
---
 phd-tests/framework/src/disk/crucible.rs  | 110 +++++++++++++++-------
 phd-tests/framework/src/disk/mod.rs       |  11 ++-
 phd-tests/framework/src/lib.rs            |  21 ++++-
 phd-tests/framework/src/test_vm/config.rs |   2 +-
 phd-tests/framework/src/test_vm/mod.rs    |  28 ++++++
 phd-tests/framework/src/test_vm/spec.rs   |   9 ++
 phd-tests/tests/src/crucible/smoke.rs     |  35 +++++++
 7 files changed, 178 insertions(+), 38 deletions(-)

diff --git a/phd-tests/framework/src/disk/crucible.rs b/phd-tests/framework/src/disk/crucible.rs
index 36fec53c7..4df13f903 100644
--- a/phd-tests/framework/src/disk/crucible.rs
+++ b/phd-tests/framework/src/disk/crucible.rs
@@ -8,7 +8,7 @@ use std::{
     net::{Ipv4Addr, SocketAddr, SocketAddrV4},
     path::{Path, PathBuf},
     process::Stdio,
-    sync::atomic::{AtomicU64, Ordering},
+    sync::Mutex,
 };
 
 use anyhow::Context;
@@ -62,9 +62,70 @@ impl Drop for Downstairs {
 /// An RAII wrapper around a Crucible disk.
 #[derive(Debug)]
 pub struct CrucibleDisk {
-    /// The name to use in instance specs that include this disk.
     device_name: DeviceName,
+    inner: Mutex<Inner>,
+}
+
+impl CrucibleDisk {
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn new(
+        device_name: DeviceName,
+        min_disk_size_gib: u64,
+        block_size: BlockSize,
+        downstairs_binary_path: &impl AsRef<std::ffi::OsStr>,
+        downstairs_ports: &[u16],
+        data_dir_root: &impl AsRef<Path>,
+        read_only_parent: Option<&impl AsRef<Path>>,
+        guest_os: Option<GuestOsKind>,
+        log_mode: ServerLogMode,
+    ) -> anyhow::Result<Self> {
+        Ok(Self {
+            device_name,
+            inner: Mutex::new(Inner::new(
+                min_disk_size_gib,
+                block_size,
+                downstairs_binary_path,
+                downstairs_ports,
+                data_dir_root,
+                read_only_parent,
+                guest_os,
+                log_mode,
+            )?),
+        })
+    }
+
+    /// Obtains the current volume construction request for this disk.
+    pub fn vcr(&self) -> VolumeConstructionRequest {
+        self.inner.lock().unwrap().vcr()
+    }
+
+    /// Sets the generation number to use in subsequent calls to create a
+    /// backend spec for this disk.
+    pub fn set_generation(&self, generation: u64) {
+        self.inner.lock().unwrap().generation = generation;
+    }
+}
+
+impl super::DiskConfig for CrucibleDisk {
+    fn device_name(&self) -> &DeviceName {
+        &self.device_name
+    }
+
+    fn backend_spec(&self) -> ComponentV0 {
+        self.inner.lock().unwrap().backend_spec()
+    }
+
+    fn guest_os(&self) -> Option<GuestOsKind> {
+        self.inner.lock().unwrap().guest_os()
+    }
+
+    fn as_crucible(&self) -> Option<&CrucibleDisk> {
+        Some(self)
+    }
+}
 
+#[derive(Debug)]
+struct Inner {
     /// The UUID to insert into this disk's `VolumeConstructionRequest`s.
     id: Uuid,
 
@@ -91,15 +152,14 @@ pub struct CrucibleDisk {
 
     /// The generation number to insert into this disk's
     /// `VolumeConstructionRequest`s.
-    generation: AtomicU64,
+    generation: u64,
 }
 
-impl CrucibleDisk {
+impl Inner {
     /// Constructs a new Crucible disk that stores its files in the supplied
     /// `data_dir`.
     #[allow(clippy::too_many_arguments)]
     pub(crate) fn new(
-        device_name: DeviceName,
         min_disk_size_gib: u64,
         block_size: BlockSize,
         downstairs_binary_path: &impl AsRef<std::ffi::OsStr>,
@@ -253,7 +313,6 @@ impl CrucibleDisk {
         }
 
         Ok(Self {
-            device_name,
             id: disk_uuid,
             block_size,
             blocks_per_extent,
@@ -270,28 +329,25 @@ impl CrucibleDisk {
                 },
             ),
             guest_os,
-            generation: AtomicU64::new(1),
+            generation: 1,
         })
     }
 
-    /// Sets the generation number to use in subsequent calls to create a
-    /// backend spec for this disk.
-    pub fn set_generation(&self, gen: u64) {
-        self.generation.store(gen, Ordering::Relaxed);
-    }
-}
+    fn backend_spec(&self) -> ComponentV0 {
+        let vcr = self.vcr();
 
-impl super::DiskConfig for CrucibleDisk {
-    fn device_name(&self) -> &DeviceName {
-        &self.device_name
+        ComponentV0::CrucibleStorageBackend(CrucibleStorageBackend {
+            request_json: serde_json::to_string(&vcr)
+                .expect("VolumeConstructionRequest should serialize"),
+            readonly: false,
+        })
     }
 
-    fn backend_spec(&self) -> ComponentV0 {
-        let gen = self.generation.load(Ordering::Relaxed);
+    fn vcr(&self) -> VolumeConstructionRequest {
         let downstairs_addrs =
             self.downstairs_instances.iter().map(|ds| ds.address).collect();
 
-        let vcr = VolumeConstructionRequest::Volume {
+        VolumeConstructionRequest::Volume {
             id: self.id,
             block_size: self.block_size.bytes(),
             sub_volumes: vec![VolumeConstructionRequest::Region {
@@ -299,7 +355,7 @@ impl super::DiskConfig for CrucibleDisk {
                 blocks_per_extent: self.blocks_per_extent,
                 extent_count: self.extent_count,
                 opts: CrucibleOpts {
-                    id: Uuid::new_v4(),
+                    id: self.id,
                     target: downstairs_addrs,
                     lossy: false,
                     flush_timeout: None,
@@ -310,7 +366,7 @@ impl super::DiskConfig for CrucibleDisk {
                     control: None,
                     read_only: false,
                 },
-                gen,
+                r#gen: self.generation,
             }],
             read_only_parent: self.read_only_parent.as_ref().map(|p| {
                 Box::new(VolumeConstructionRequest::File {
@@ -319,22 +375,12 @@ impl super::DiskConfig for CrucibleDisk {
                     path: p.to_string_lossy().to_string(),
                 })
             }),
-        };
-
-        ComponentV0::CrucibleStorageBackend(CrucibleStorageBackend {
-            request_json: serde_json::to_string(&vcr)
-                .expect("VolumeConstructionRequest should serialize"),
-            readonly: false,
-        })
+        }
     }
 
     fn guest_os(&self) -> Option<GuestOsKind> {
         self.guest_os
     }
-
-    fn as_crucible(&self) -> Option<&CrucibleDisk> {
-        Some(self)
-    }
 }
 
 fn run_crucible_downstairs(
diff --git a/phd-tests/framework/src/disk/mod.rs b/phd-tests/framework/src/disk/mod.rs
index 5d91e74cf..0fbb542c4 100644
--- a/phd-tests/framework/src/disk/mod.rs
+++ b/phd-tests/framework/src/disk/mod.rs
@@ -279,6 +279,8 @@ impl DiskFactory {
         mut min_disk_size_gib: u64,
         block_size: BlockSize,
     ) -> Result<Arc<CrucibleDisk>, DiskError> {
+        const BYTES_PER_GIB: u64 = 1024 * 1024 * 1024;
+
         let binary_path = self.artifact_store.get_crucible_downstairs().await?;
 
         let (artifact_path, guest_os) = match source {
@@ -287,8 +289,13 @@ impl DiskFactory {
                 (Some(path), Some(os))
             }
             DiskSource::Blank(size) => {
-                min_disk_size_gib = min_disk_size_gib
-                    .max(u64::try_from(*size).map_err(anyhow::Error::from)?);
+                let blank_size =
+                    u64::try_from(*size).map_err(anyhow::Error::from)?;
+
+                let min_disk_size_b =
+                    (min_disk_size_gib * BYTES_PER_GIB).max(blank_size);
+
+                min_disk_size_gib = min_disk_size_b.div_ceil(BYTES_PER_GIB);
                 (None, None)
             }
             // It's possible in theory to have a Crucible-backed disk with
diff --git a/phd-tests/framework/src/lib.rs b/phd-tests/framework/src/lib.rs
index 912ff397c..327b1804b 100644
--- a/phd-tests/framework/src/lib.rs
+++ b/phd-tests/framework/src/lib.rs
@@ -252,12 +252,27 @@ impl Framework {
         config: &VmConfig<'_>,
         environment: Option<&EnvironmentSpec>,
     ) -> anyhow::Result<TestVm> {
-        TestVm::new(
-            self,
+        self.spawn_vm_with_spec(
             config
                 .vm_spec(self)
                 .await
-                .context("building VM config for test VM")?,
+                .context("building VM spec from VmConfig")?,
+            environment,
+        )
+        .await
+    }
+
+    /// Spawns a new test VM using the supplied `spec`. If `environment` is
+    /// `Some`, the VM is spawned using the supplied environment; otherwise it
+    /// is spawned using the default `environment_builder`.
+    pub async fn spawn_vm_with_spec(
+        &self,
+        spec: VmSpec,
+        environment: Option<&EnvironmentSpec>,
+    ) -> anyhow::Result<TestVm> {
+        TestVm::new(
+            self,
+            spec,
             environment.unwrap_or(&self.environment_builder()),
         )
         .await
diff --git a/phd-tests/framework/src/test_vm/config.rs b/phd-tests/framework/src/test_vm/config.rs
index cb586fd8a..06a0c7c2d 100644
--- a/phd-tests/framework/src/test_vm/config.rs
+++ b/phd-tests/framework/src/test_vm/config.rs
@@ -208,7 +208,7 @@ impl<'dr> VmConfig<'dr> {
         self
     }
 
-    pub(crate) async fn vm_spec(
+    pub async fn vm_spec(
         &self,
         framework: &Framework,
     ) -> anyhow::Result<VmSpec> {
diff --git a/phd-tests/framework/src/test_vm/mod.rs b/phd-tests/framework/src/test_vm/mod.rs
index caa17360e..08b57e889 100644
--- a/phd-tests/framework/src/test_vm/mod.rs
+++ b/phd-tests/framework/src/test_vm/mod.rs
@@ -11,6 +11,7 @@ use std::{
 };
 
 use crate::{
+    disk::{crucible::CrucibleDisk, DiskConfig},
     guest_os::{
         self, windows::WindowsVm, CommandSequence, CommandSequenceEntry,
         GuestOs, GuestOsKind,
@@ -603,6 +604,33 @@ impl TestVm {
         Ok(self.client.instance_migrate_status().send().await?.into_inner())
     }
 
+    pub async fn replace_crucible_vcr(
+        &self,
+        disk: &CrucibleDisk,
+    ) -> anyhow::Result<()> {
+        let vcr = disk.vcr();
+        let body = propolis_client::types::InstanceVcrReplace {
+            vcr_json: serde_json::to_string(&vcr)
+                .with_context(|| format!("serializing VCR {vcr:?}"))?,
+        };
+
+        let response_value = self
+            .client
+            .instance_issue_crucible_vcr_request()
+            .id(disk.device_name().clone().into_backend_name().into_string())
+            .body(body)
+            .send()
+            .await?;
+
+        anyhow::ensure!(
+            response_value.status().is_success(),
+            "VCR replacement request returned an error value: \
+            {response_value:?}"
+        );
+
+        Ok(())
+    }
+
     pub async fn get_serial_console_history(
         &self,
         from_start: u64,
diff --git a/phd-tests/framework/src/test_vm/spec.rs b/phd-tests/framework/src/test_vm/spec.rs
index 573537c08..6a30c3c9d 100644
--- a/phd-tests/framework/src/test_vm/spec.rs
+++ b/phd-tests/framework/src/test_vm/spec.rs
@@ -34,6 +34,15 @@ pub struct VmSpec {
 }
 
 impl VmSpec {
+    /// Yields an array of handles to this VM's Crucible disks.
+    pub fn get_crucible_disks(&self) -> Vec<Arc<dyn disk::DiskConfig>> {
+        self.disk_handles
+            .iter()
+            .filter(|d| d.as_crucible().is_some())
+            .cloned()
+            .collect()
+    }
+
     /// Update the Crucible backend specs in the instance spec to match the
     /// current backend specs given by this specification's disk handles.
     pub(crate) fn refresh_crucible_backends(&mut self) {
diff --git a/phd-tests/tests/src/crucible/smoke.rs b/phd-tests/tests/src/crucible/smoke.rs
index a27134579..c2157475d 100644
--- a/phd-tests/tests/src/crucible/smoke.rs
+++ b/phd-tests/tests/src/crucible/smoke.rs
@@ -4,6 +4,10 @@
 
 use std::time::Duration;
 
+use phd_framework::{
+    disk::{BlockSize, DiskSource},
+    test_vm::{DiskBackend, DiskInterface},
+};
 use phd_testcase::*;
 use propolis_client::types::InstanceState;
 
@@ -82,3 +86,34 @@ async fn shutdown_persistence_test(ctx: &Framework) {
     let lsout = vm.run_shell_command("ls foo.bar 2> /dev/null").await?;
     assert_eq!(lsout, "foo.bar");
 }
+
+#[phd_testcase]
+async fn vcr_replace_test(ctx: &Framework) {
+    let mut config = ctx.vm_config_builder("crucible_vcr_replace_test");
+
+    // Create a blank data disk on which to perform VCR replacement. This is
+    // necessary because Crucible doesn't permit VCR replacements for volumes
+    // whose read-only parents are local files (which is true for artifact-based
+    // Crucible disks).
+    config.data_disk(
+        "vcr-replacement-target",
+        DiskSource::Blank(1024 * 1024 * 1024),
+        DiskInterface::Nvme,
+        DiskBackend::Crucible {
+            min_disk_size_gib: 1,
+            block_size: BlockSize::Bytes512,
+        },
+        5,
+    );
+
+    let spec = config.vm_spec(ctx).await?;
+    let disks = spec.get_crucible_disks();
+    let disk = disks[0].as_crucible().unwrap();
+
+    let mut vm = ctx.spawn_vm_with_spec(spec, None).await?;
+    vm.launch().await?;
+    vm.wait_to_boot().await?;
+
+    disk.set_generation(2);
+    vm.replace_crucible_vcr(disk).await?;
+}

From 23e73f2752fc5291fe2683c66df50905b4375be9 Mon Sep 17 00:00:00 2001
From: Greg Colombo <greg@oxidecomputer.com>
Date: Tue, 25 Feb 2025 21:09:26 +0000
Subject: [PATCH 2/4] phd: better method for referencing a disk from a VmSpec

---
 phd-tests/framework/src/test_vm/spec.rs | 10 +++++-----
 phd-tests/tests/src/crucible/smoke.rs   |  8 +++++---
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/phd-tests/framework/src/test_vm/spec.rs b/phd-tests/framework/src/test_vm/spec.rs
index 6a30c3c9d..970e4424d 100644
--- a/phd-tests/framework/src/test_vm/spec.rs
+++ b/phd-tests/framework/src/test_vm/spec.rs
@@ -34,13 +34,13 @@ pub struct VmSpec {
 }
 
 impl VmSpec {
-    /// Yields an array of handles to this VM's Crucible disks.
-    pub fn get_crucible_disks(&self) -> Vec<Arc<dyn disk::DiskConfig>> {
+    pub fn get_disk_by_device_name(
+        &self,
+        name: &str,
+    ) -> Option<&Arc<dyn disk::DiskConfig>> {
         self.disk_handles
             .iter()
-            .filter(|d| d.as_crucible().is_some())
-            .cloned()
-            .collect()
+            .find(|disk| disk.device_name().as_str() == name)
     }
 
     /// Update the Crucible backend specs in the instance spec to match the
diff --git a/phd-tests/tests/src/crucible/smoke.rs b/phd-tests/tests/src/crucible/smoke.rs
index c2157475d..fd34e34b8 100644
--- a/phd-tests/tests/src/crucible/smoke.rs
+++ b/phd-tests/tests/src/crucible/smoke.rs
@@ -95,8 +95,9 @@ async fn vcr_replace_test(ctx: &Framework) {
     // necessary because Crucible doesn't permit VCR replacements for volumes
     // whose read-only parents are local files (which is true for artifact-based
     // Crucible disks).
+    const DATA_DISK_NAME: &str = "vcr-replacement-target";
     config.data_disk(
-        "vcr-replacement-target",
+        DATA_DISK_NAME,
         DiskSource::Blank(1024 * 1024 * 1024),
         DiskInterface::Nvme,
         DiskBackend::Crucible {
@@ -107,8 +108,9 @@ async fn vcr_replace_test(ctx: &Framework) {
     );
 
     let spec = config.vm_spec(ctx).await?;
-    let disks = spec.get_crucible_disks();
-    let disk = disks[0].as_crucible().unwrap();
+    let disk_hdl =
+        spec.get_disk_by_device_name(DATA_DISK_NAME).cloned().unwrap();
+    let disk = disk_hdl.as_crucible().unwrap();
 
     let mut vm = ctx.spawn_vm_with_spec(spec, None).await?;
     vm.launch().await?;

From fa04d6f606e0b01381b088cdb7ebb93e470e14b2 Mon Sep 17 00:00:00 2001
From: Greg Colombo <greg@oxidecomputer.com>
Date: Tue, 25 Feb 2025 21:14:22 +0000
Subject: [PATCH 3/4] phd: improve logging

---
 phd-tests/framework/src/test_vm/mod.rs | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/phd-tests/framework/src/test_vm/mod.rs b/phd-tests/framework/src/test_vm/mod.rs
index 08b57e889..88fcfb2d9 100644
--- a/phd-tests/framework/src/test_vm/mod.rs
+++ b/phd-tests/framework/src/test_vm/mod.rs
@@ -614,6 +614,12 @@ impl TestVm {
                 .with_context(|| format!("serializing VCR {vcr:?}"))?,
         };
 
+        info!(
+            disk_name = disk.device_name().as_str(),
+            vcr = ?vcr,
+            "issuing Crucible VCR replacement request"
+        );
+
         let response_value = self
             .client
             .instance_issue_crucible_vcr_request()

From ca88843a08bd7246f1133377942a5076361b8a1e Mon Sep 17 00:00:00 2001
From: Greg Colombo <greg@oxidecomputer.com>
Date: Tue, 25 Feb 2025 21:14:22 +0000
Subject: [PATCH 4/4] phd: put less Crucible state into the inner struct

---
 phd-tests/framework/src/disk/crucible.rs | 34 +++++++++---------------
 1 file changed, 12 insertions(+), 22 deletions(-)

diff --git a/phd-tests/framework/src/disk/crucible.rs b/phd-tests/framework/src/disk/crucible.rs
index 4df13f903..5ac2f2747 100644
--- a/phd-tests/framework/src/disk/crucible.rs
+++ b/phd-tests/framework/src/disk/crucible.rs
@@ -63,6 +63,8 @@ impl Drop for Downstairs {
 #[derive(Debug)]
 pub struct CrucibleDisk {
     device_name: DeviceName,
+    disk_id: Uuid,
+    guest_os: Option<GuestOsKind>,
     inner: Mutex<Inner>,
 }
 
@@ -81,6 +83,8 @@ impl CrucibleDisk {
     ) -> anyhow::Result<Self> {
         Ok(Self {
             device_name,
+            disk_id: Uuid::new_v4(),
+            guest_os,
             inner: Mutex::new(Inner::new(
                 min_disk_size_gib,
                 block_size,
@@ -88,7 +92,6 @@ impl CrucibleDisk {
                 downstairs_ports,
                 data_dir_root,
                 read_only_parent,
-                guest_os,
                 log_mode,
             )?),
         })
@@ -96,7 +99,7 @@ impl CrucibleDisk {
 
     /// Obtains the current volume construction request for this disk.
     pub fn vcr(&self) -> VolumeConstructionRequest {
-        self.inner.lock().unwrap().vcr()
+        self.inner.lock().unwrap().vcr(self.disk_id)
     }
 
     /// Sets the generation number to use in subsequent calls to create a
@@ -112,11 +115,11 @@ impl super::DiskConfig for CrucibleDisk {
     }
 
     fn backend_spec(&self) -> ComponentV0 {
-        self.inner.lock().unwrap().backend_spec()
+        self.inner.lock().unwrap().backend_spec(self.disk_id)
     }
 
     fn guest_os(&self) -> Option<GuestOsKind> {
-        self.inner.lock().unwrap().guest_os()
+        self.guest_os
     }
 
     fn as_crucible(&self) -> Option<&CrucibleDisk> {
@@ -126,9 +129,6 @@ impl super::DiskConfig for CrucibleDisk {
 
 #[derive(Debug)]
 struct Inner {
-    /// The UUID to insert into this disk's `VolumeConstructionRequest`s.
-    id: Uuid,
-
     /// The disk's block size.
     block_size: BlockSize,
 
@@ -144,9 +144,6 @@ struct Inner {
     /// An optional path to a file to use as a read-only parent for this disk.
     read_only_parent: Option<PathBuf>,
 
-    /// The kind of guest OS that can be found on this disk, if there is one.
-    guest_os: Option<GuestOsKind>,
-
     /// The base64-encoded encryption key to use for this disk.
     encryption_key: String,
 
@@ -166,7 +163,6 @@ impl Inner {
         downstairs_ports: &[u16],
         data_dir_root: &impl AsRef<Path>,
         read_only_parent: Option<&impl AsRef<Path>>,
-        guest_os: Option<GuestOsKind>,
         log_mode: ServerLogMode,
     ) -> anyhow::Result<Self> {
         // To create a region, Crucible requires a block size, an extent size
@@ -313,7 +309,6 @@ impl Inner {
         }
 
         Ok(Self {
-            id: disk_uuid,
             block_size,
             blocks_per_extent,
             extent_count: extents_in_disk as u32,
@@ -328,13 +323,12 @@ impl Inner {
                     bytes
                 },
             ),
-            guest_os,
             generation: 1,
         })
     }
 
-    fn backend_spec(&self) -> ComponentV0 {
-        let vcr = self.vcr();
+    fn backend_spec(&self, disk_id: Uuid) -> ComponentV0 {
+        let vcr = self.vcr(disk_id);
 
         ComponentV0::CrucibleStorageBackend(CrucibleStorageBackend {
             request_json: serde_json::to_string(&vcr)
@@ -343,19 +337,19 @@ impl Inner {
         })
     }
 
-    fn vcr(&self) -> VolumeConstructionRequest {
+    fn vcr(&self, disk_id: Uuid) -> VolumeConstructionRequest {
         let downstairs_addrs =
             self.downstairs_instances.iter().map(|ds| ds.address).collect();
 
         VolumeConstructionRequest::Volume {
-            id: self.id,
+            id: disk_id,
             block_size: self.block_size.bytes(),
             sub_volumes: vec![VolumeConstructionRequest::Region {
                 block_size: self.block_size.bytes(),
                 blocks_per_extent: self.blocks_per_extent,
                 extent_count: self.extent_count,
                 opts: CrucibleOpts {
-                    id: self.id,
+                    id: disk_id,
                     target: downstairs_addrs,
                     lossy: false,
                     flush_timeout: None,
@@ -377,10 +371,6 @@ impl Inner {
             }),
         }
     }
-
-    fn guest_os(&self) -> Option<GuestOsKind> {
-        self.guest_os
-    }
 }
 
 fn run_crucible_downstairs(