diff --git a/illumos-utils/src/zfs.rs b/illumos-utils/src/zfs.rs index 76ec405422..d9220ed264 100644 --- a/illumos-utils/src/zfs.rs +++ b/illumos-utils/src/zfs.rs @@ -144,6 +144,12 @@ pub struct EncryptionDetails { pub epoch: u64, } +#[derive(Debug, Default)] +pub struct SizeDetails { + pub quota: Option, + pub compression: Option<&'static str>, +} + #[cfg_attr(any(test, feature = "testing"), mockall::automock, allow(dead_code))] impl Zfs { /// Lists all datasets within a pool or existing dataset. @@ -192,10 +198,16 @@ impl Zfs { zoned: bool, do_format: bool, encryption_details: Option, - quota: Option, + size_details: Option, ) -> Result<(), EnsureFilesystemError> { let (exists, mounted) = Self::dataset_exists(name, &mountpoint)?; if exists { + if let Some(SizeDetails { quota, compression }) = size_details { + // apply quota and compression mode (in case they've changed across + // sled-agent versions since creation) + Self::apply_properties(name, &mountpoint, quota, compression)?; + } + if encryption_details.is_none() { // If the dataset exists, we're done. Unencrypted datasets are // automatically mounted. @@ -238,6 +250,7 @@ impl Zfs { &epoch, ]); } + cmd.args(&["-o", &format!("mountpoint={}", mountpoint), name]); execute(cmd).map_err(|err| EnsureFilesystemError { name: name.to_string(), @@ -245,14 +258,38 @@ impl Zfs { err: err.into(), })?; - // Apply any quota. + if let Some(SizeDetails { quota, compression }) = size_details { + // Apply any quota and compression mode. + Self::apply_properties(name, &mountpoint, quota, compression)?; + } + + Ok(()) + } + + fn apply_properties( + name: &str, + mountpoint: &Mountpoint, + quota: Option, + compression: Option<&'static str>, + ) -> Result<(), EnsureFilesystemError> { if let Some(quota) = quota { if let Err(err) = Self::set_value(name, "quota", &format!("{quota}")) { return Err(EnsureFilesystemError { name: name.to_string(), - mountpoint, + mountpoint: mountpoint.clone(), + // Take the execution error from the SetValueError + err: err.err.into(), + }); + } + } + if let Some(compression) = compression { + if let Err(err) = Self::set_value(name, "compression", compression) + { + return Err(EnsureFilesystemError { + name: name.to_string(), + mountpoint: mountpoint.clone(), // Take the execution error from the SetValueError err: err.err.into(), }); diff --git a/sled-agent/src/storage_manager.rs b/sled-agent/src/storage_manager.rs index 27591156d2..b186b1c95f 100644 --- a/sled-agent/src/storage_manager.rs +++ b/sled-agent/src/storage_manager.rs @@ -368,14 +368,14 @@ impl StorageWorker { let fs_name = &dataset_name.full(); let do_format = true; let encryption_details = None; - let quota = None; + let size_details = None; Zfs::ensure_filesystem( &dataset_name.full(), Mountpoint::Path(Utf8PathBuf::from("/data")), zoned, do_format, encryption_details, - quota, + size_details, )?; // Ensure the dataset has a usable UUID. if let Ok(id_str) = Zfs::get_oxide_value(&fs_name, "uuid") { diff --git a/sled-hardware/src/disk.rs b/sled-hardware/src/disk.rs index f19b0592fd..5ff555cd84 100644 --- a/sled-hardware/src/disk.rs +++ b/sled-hardware/src/disk.rs @@ -9,6 +9,7 @@ use illumos_utils::zfs::DestroyDatasetErrorVariant; use illumos_utils::zfs::EncryptionDetails; use illumos_utils::zfs::Keypath; use illumos_utils::zfs::Mountpoint; +use illumos_utils::zfs::SizeDetails; use illumos_utils::zfs::Zfs; use illumos_utils::zpool::Zpool; use illumos_utils::zpool::ZpoolKind; @@ -225,11 +226,13 @@ struct ExpectedDataset { quota: Option, // Identifies if the dataset should be deleted on boot wipe: bool, + // Optional compression mode + compression: Option<&'static str>, } impl ExpectedDataset { const fn new(name: &'static str) -> Self { - ExpectedDataset { name, quota: None, wipe: false } + ExpectedDataset { name, quota: None, wipe: false, compression: None } } const fn quota(mut self, quota: usize) -> Self { @@ -241,6 +244,11 @@ impl ExpectedDataset { self.wipe = true; self } + + const fn compression(mut self, compression: &'static str) -> Self { + self.compression = Some(compression); + self + } } pub const INSTALL_DATASET: &'static str = "install"; @@ -251,6 +259,10 @@ pub const DEBUG_DATASET: &'static str = "debug"; // TODO-correctness: This value of 100GiB is a pretty wild guess, and should be // tuned as needed. pub const DEBUG_DATASET_QUOTA: usize = 100 * (1 << 30); +// ditto. +pub const DUMP_DATASET_QUOTA: usize = 100 * (1 << 30); +// passed to zfs create -o compression= +pub const DUMP_DATASET_COMPRESSION: &'static str = "gzip-9"; // U.2 datasets live under the encrypted dataset and inherit encryption pub const ZONE_DATASET: &'static str = "crypt/zone"; @@ -264,7 +276,9 @@ static U2_EXPECTED_DATASETS: [ExpectedDataset; U2_EXPECTED_DATASET_COUNT] = [ // Stores filesystems for zones ExpectedDataset::new(ZONE_DATASET).wipe(), // For storing full kernel RAM dumps - ExpectedDataset::new(DUMP_DATASET), + ExpectedDataset::new(DUMP_DATASET) + .quota(DUMP_DATASET_QUOTA) + .compression(DUMP_DATASET_COMPRESSION), ]; const M2_EXPECTED_DATASET_COUNT: usize = 5; @@ -562,13 +576,17 @@ impl Disk { } let encryption_details = None; + let size_details = Some(SizeDetails { + quota: dataset.quota, + compression: dataset.compression, + }); Zfs::ensure_filesystem( name, Mountpoint::Path(mountpoint), zoned, do_format, encryption_details, - dataset.quota, + size_details, )?; if dataset.wipe {