diff --git a/src/zpool/mod.rs b/src/zpool/mod.rs index 2e3cea4..592b295 100644 --- a/src/zpool/mod.rs +++ b/src/zpool/mod.rs @@ -422,7 +422,7 @@ pub trait ZpoolEngine { /// Add a ZIL to existing Zpool. /// /// * `name` - Name of the zpool - /// * `new_zil` - VDEV to use as ZIL + /// * `new_zil` - A VDEV to use as ZIL /// * `add_mode` - Disable some safety checks fn add_zil>( &self, @@ -434,7 +434,7 @@ pub trait ZpoolEngine { /// Add a cache to existing Zpool. /// /// * `name` - Name of the zpool - /// * `new_cache` - Disk to use as cache + /// * `new_cache` - A disk to use as cache /// * `add_mode` - Disable some safety checks fn add_cache, D: AsRef>( &self, @@ -446,7 +446,7 @@ pub trait ZpoolEngine { /// Add a spare to existing Zpool. /// /// * `name` - Name of the zpool - /// * `new_spare` - Disk to use as spare + /// * `new_spare` - A disk to use as spare /// * `add_mode` - Disable some safety checks fn add_spare, D: AsRef>( &self, @@ -455,6 +455,17 @@ pub trait ZpoolEngine { add_mode: CreateMode, ) -> ZpoolResult<()>; + /// [Replace](https://docs.oracle.com/cd/E19253-01/819-5461/gazgd/index.html) a device with another. + /// + /// * `old_disk` - A disk to be replaced. + /// * `new_disk` - A new disk. + fn replace_disk, D: AsRef, O: AsRef>( + &self, + name: N, + old_disk: D, + new_disk: O, + ) -> ZpoolResult<()>; + /// Remove Spare, Cache or log device /// /// * `name` - Name of the zpool diff --git a/src/zpool/open3.rs b/src/zpool/open3.rs index e50fa99..a9a5de2 100644 --- a/src/zpool/open3.rs +++ b/src/zpool/open3.rs @@ -483,6 +483,26 @@ impl ZpoolEngine for ZpoolOpen3 { } } + fn replace_disk, D: AsRef, O: AsRef>( + &self, + name: N, + old_disk: D, + new_disk: O, + ) -> Result<(), ZpoolError> { + let mut z = self.zpool(); + z.arg("replace"); + z.arg(name.as_ref()); + z.arg(old_disk.as_ref()); + z.arg(new_disk.as_ref()); + debug!(self.logger, "executing"; "cmd" => format_args!("{:?}", z)); + let out = z.output()?; + if out.status.success() { + Ok(()) + } else { + Err(ZpoolError::from_stderr(&out.stderr)) + } + } + fn remove, D: AsRef>(&self, name: N, device: D) -> ZpoolResult<()> { let mut z = self.zpool(); z.arg("remove"); diff --git a/tests/test_zpool.rs b/tests/test_zpool.rs index 1d9338f..d0cc74a 100644 --- a/tests/test_zpool.rs +++ b/tests/test_zpool.rs @@ -853,3 +853,39 @@ fn test_zpool_add_spare() { assert_eq!(topo_expected, z); }); } + +#[test] +fn test_zpool_replace_disk() { + use std::{thread, time}; + + run_test(|name| { + let zpool = ZpoolOpen3::default(); + let vdev0_path = setup_vdev("/vdevs/vdev0", &Bytes::MegaBytes(64 + 10)); + let vdev1_path = setup_vdev("/vdevs/vdev1", &Bytes::MegaBytes(64 + 10)); + let vdev2_path = setup_vdev("/vdevs/vdev2", &Bytes::MegaBytes(64 + 10)); + let topo = CreateZpoolRequestBuilder::default() + .name(name.clone()) + .create_mode(CreateMode::Force) + .vdev(CreateVdevRequest::Mirror(vec![vdev0_path.clone(), vdev1_path.clone()])) + .build() + .unwrap(); + zpool.create(topo.clone()).unwrap(); + + let result = zpool.replace_disk(&name, &vdev0_path, &vdev2_path); + assert!(result.is_ok()); + + let topo_expected = CreateZpoolRequestBuilder::default() + .name(name.clone()) + .create_mode(CreateMode::Force) + .vdev(CreateVdevRequest::Mirror(vec![vdev2_path.clone(), vdev1_path.clone()])) + .build() + .unwrap(); + + // otherwise test _might_ fail. + let wait_time = time::Duration::from_secs(13); + thread::sleep(wait_time); + + let z = zpool.status(&name).unwrap(); + assert_eq!(topo_expected, z); + }); +}