Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up "Zfs::ensure_filesystem" #7246

Merged
merged 18 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 73 additions & 65 deletions illumos-utils/src/zfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,10 @@ pub struct DestroyDatasetError {
}

#[derive(thiserror::Error, Debug)]
enum EnsureFilesystemErrorRaw {
enum EnsureDatasetErrorRaw {
#[error("ZFS execution error: {0}")]
Execution(#[from] crate::ExecutionError),

#[error("Filesystem does not exist, and formatting was not requested")]
NotFoundNotFormatted,

#[error("Unexpected output from ZFS commands: {0}")]
Output(String),

Expand All @@ -82,13 +79,13 @@ enum EnsureFilesystemErrorRaw {
MountOverlayFsFailed(crate::ExecutionError),
}

/// Error returned by [`Zfs::ensure_filesystem`].
/// Error returned by [`Zfs::ensure_dataset`].
#[derive(thiserror::Error, Debug)]
#[error("Failed to ensure filesystem '{name}': {err}")]
pub struct EnsureFilesystemError {
pub struct EnsureDatasetError {
name: String,
#[source]
err: EnsureFilesystemErrorRaw,
err: EnsureDatasetErrorRaw,
}

/// Error returned by [`Zfs::set_oxide_value`]
Expand Down Expand Up @@ -418,6 +415,49 @@ fn build_zfs_set_key_value_pairs(
props
}

/// Arguments to [Zfs::ensure_dataset].
pub struct DatasetEnsureArgs<'a> {
/// The full path of the ZFS dataset.
pub name: &'a str,

/// The expected mountpoint of this filesystem.
/// If the filesystem already exists, and is not mounted here, an error is
/// returned.
pub mountpoint: Mountpoint,

/// Identifies whether or not this filesystem should be
/// used in a zone. Only used when creating a new filesystem - ignored
/// if the filesystem already exists.
pub zoned: bool,

/// Ensures a filesystem as an encryption root.
///
/// For new filesystems, this supplies the key, and all datasets within this
/// root are implicitly encrypted. For existing filesystems, ensures that
/// they are mounted (and that keys are loaded), but does not verify the
/// input details.
pub encryption_details: Option<EncryptionDetails>,

/// Optional properties that can be set for the dataset regarding
/// space usage.
///
/// Can be used to change settings on new or existing datasets.
pub size_details: Option<SizeDetails>,

/// An optional UUID of the dataset.
///
/// If provided, this is set as the value "oxide:uuid" through "zfs set".
///
/// Can be used to change settings on new or existing datasets.
pub id: Option<DatasetUuid>,

/// ZFS options passed to "zfs create" with the "-o" flag.
///
/// Only used when the filesystem is being created.
/// Each string in this optional Vec should have the format "key=value".
pub additional_options: Option<Vec<String>>,
}

impl Zfs {
/// Lists all datasets within a pool or existing dataset.
///
Expand Down Expand Up @@ -513,44 +553,26 @@ impl Zfs {
Ok(())
}

/// Creates a new ZFS filesystem unless one already exists.
/// Creates a new ZFS dataset unless one already exists.
///
/// - `name`: the full path to the zfs dataset
/// - `mountpoint`: The expected mountpoint of this filesystem.
/// If the filesystem already exists, and is not mounted here, and error is
/// returned.
/// - `zoned`: identifies whether or not this filesystem should be
/// used in a zone. Only used when creating a new filesystem - ignored
/// if the filesystem already exists.
/// - `do_format`: if "false", prevents a new filesystem from being created,
/// and returns an error if it is not found.
/// - `encryption_details`: Ensures a filesystem as an encryption root.
/// For new filesystems, this supplies the key, and all datasets within this
/// root are implicitly encrypted. For existing filesystems, ensures that
/// they are mounted (and that keys are loaded), but does not verify the
/// input details.
/// - `size_details`: If supplied, sets size-related information. These
/// values are set on both new filesystem creation as well as when loading
/// existing filesystems.
/// - `additional_options`: Additional ZFS options, which are only set when
/// creating new filesystems.
#[allow(clippy::too_many_arguments)]
pub fn ensure_filesystem(
name: &str,
mountpoint: Mountpoint,
zoned: bool,
do_format: bool,
encryption_details: Option<EncryptionDetails>,
size_details: Option<SizeDetails>,
id: Option<DatasetUuid>,
additional_options: Option<Vec<String>>,
) -> Result<(), EnsureFilesystemError> {
/// Refer to [DatasetEnsureArgs] for details on the supplied arguments.
pub fn ensure_dataset(
DatasetEnsureArgs {
name,
mountpoint,
zoned,
encryption_details,
size_details,
id,
additional_options,
}: DatasetEnsureArgs,
) -> Result<(), EnsureDatasetError> {
let (exists, mounted) = Self::dataset_exists(name, &mountpoint)?;

let props = build_zfs_set_key_value_pairs(size_details, id);
if exists {
Self::set_values(name, props.as_slice()).map_err(|err| {
EnsureFilesystemError {
EnsureDatasetError {
name: name.to_string(),
err: err.err.into(),
}
Expand All @@ -570,13 +592,6 @@ impl Zfs {
}
}

if !do_format {
return Err(EnsureFilesystemError {
name: name.to_string(),
err: EnsureFilesystemErrorRaw::NotFoundNotFormatted,
});
}

// If it doesn't exist, make it.
let mut command = std::process::Command::new(PFEXEC);
let cmd = command.args(&[ZFS, "create"]);
Expand Down Expand Up @@ -606,7 +621,7 @@ impl Zfs {

cmd.args(&["-o", &format!("mountpoint={}", mountpoint), name]);

execute(cmd).map_err(|err| EnsureFilesystemError {
execute(cmd).map_err(|err| EnsureDatasetError {
name: name.to_string(),
err: err.into(),
})?;
Expand All @@ -618,42 +633,35 @@ impl Zfs {
let user = whoami::username();
let mount = format!("{mountpoint}");
let cmd = command.args(["chown", "-R", &user, &mount]);
execute(cmd).map_err(|err| EnsureFilesystemError {
execute(cmd).map_err(|err| EnsureDatasetError {
name: name.to_string(),
err: err.into(),
})?;
}

Self::set_values(name, props.as_slice()).map_err(|err| {
EnsureFilesystemError {
name: name.to_string(),
err: err.err.into(),
}
EnsureDatasetError { name: name.to_string(), err: err.err.into() }
})?;

Ok(())
}

fn mount_encrypted_dataset(
name: &str,
) -> Result<(), EnsureFilesystemError> {
fn mount_encrypted_dataset(name: &str) -> Result<(), EnsureDatasetError> {
let mut command = std::process::Command::new(PFEXEC);
let cmd = command.args(&[ZFS, "mount", "-l", name]);
execute(cmd).map_err(|err| EnsureFilesystemError {
execute(cmd).map_err(|err| EnsureDatasetError {
name: name.to_string(),
err: EnsureFilesystemErrorRaw::MountEncryptedFsFailed(err),
err: EnsureDatasetErrorRaw::MountEncryptedFsFailed(err),
})?;
Ok(())
}

pub fn mount_overlay_dataset(
name: &str,
) -> Result<(), EnsureFilesystemError> {
pub fn mount_overlay_dataset(name: &str) -> Result<(), EnsureDatasetError> {
let mut command = std::process::Command::new(PFEXEC);
let cmd = command.args(&[ZFS, "mount", "-O", name]);
execute(cmd).map_err(|err| EnsureFilesystemError {
execute(cmd).map_err(|err| EnsureDatasetError {
name: name.to_string(),
err: EnsureFilesystemErrorRaw::MountOverlayFsFailed(err),
err: EnsureDatasetErrorRaw::MountOverlayFsFailed(err),
})?;
Ok(())
}
Expand All @@ -663,7 +671,7 @@ impl Zfs {
fn dataset_exists(
name: &str,
mountpoint: &Mountpoint,
) -> Result<(bool, bool), EnsureFilesystemError> {
) -> Result<(bool, bool), EnsureDatasetError> {
let mut command = std::process::Command::new(ZFS);
let cmd = command.args(&[
"list",
Expand All @@ -676,9 +684,9 @@ impl Zfs {
let stdout = String::from_utf8_lossy(&output.stdout);
let values: Vec<&str> = stdout.trim().split('\t').collect();
if &values[..3] != &[name, "filesystem", &mountpoint.to_string()] {
return Err(EnsureFilesystemError {
return Err(EnsureDatasetError {
name: name.to_string(),
err: EnsureFilesystemErrorRaw::Output(stdout.to_string()),
err: EnsureDatasetErrorRaw::Output(stdout.to_string()),
});
}
let mounted = values[3] == "yes";
Expand Down
22 changes: 11 additions & 11 deletions sled-agent/src/backing_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@

use camino::Utf8PathBuf;
use illumos_utils::zfs::{
EnsureFilesystemError, GetValueError, Mountpoint, SizeDetails, Zfs,
DatasetEnsureArgs, EnsureDatasetError, GetValueError, Mountpoint,
SizeDetails, Zfs,
};
use omicron_common::api::external::ByteCount;
use omicron_common::disk::CompressionAlgorithm;
Expand All @@ -38,7 +39,7 @@ pub enum BackingFsError {
DatasetProperty(#[from] GetValueError),

#[error("Error initializing dataset: {0}")]
Mount(#[from] EnsureFilesystemError),
Mount(#[from] EnsureDatasetError),

#[error("Failed to ensure subdirectory {0}")]
EnsureSubdir(#[from] io::Error),
Expand Down Expand Up @@ -143,16 +144,15 @@ pub(crate) fn ensure_backing_fs(
compression: bfs.compression,
});

Zfs::ensure_filesystem(
&dataset,
mountpoint.clone(),
false, // zoned
true, // do_format
None, // encryption_details,
Zfs::ensure_dataset(DatasetEnsureArgs {
name: &dataset,
mountpoint: mountpoint.clone(),
zoned: false,
encryption_details: None,
size_details,
None,
Some(vec!["canmount=noauto".to_string()]), // options
)?;
id: None,
additional_options: Some(vec!["canmount=noauto".to_string()]),
})?;

// Check if a ZFS filesystem is already mounted on bfs.mountpoint by
// retrieving the ZFS `mountpoint` property and comparing it. This
Expand Down
23 changes: 9 additions & 14 deletions sled-agent/src/bootstrap/pre_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,22 +279,17 @@ fn ensure_zfs_key_directory_exists(log: &Logger) -> Result<(), StartError> {
}

fn ensure_zfs_ramdisk_dataset() -> Result<(), StartError> {
let zoned = false;
let do_format = true;
let encryption_details = None;
let quota = None;
Zfs::ensure_filesystem(
zfs::ZONE_ZFS_RAMDISK_DATASET,
zfs::Mountpoint::Path(Utf8PathBuf::from(
Zfs::ensure_dataset(zfs::DatasetEnsureArgs {
name: zfs::ZONE_ZFS_RAMDISK_DATASET,
mountpoint: zfs::Mountpoint::Path(Utf8PathBuf::from(
zfs::ZONE_ZFS_RAMDISK_DATASET_MOUNTPOINT,
)),
zoned,
do_format,
encryption_details,
quota,
None,
None,
)
zoned: false,
encryption_details: None,
size_details: None,
id: None,
additional_options: None,
})
.map_err(StartError::EnsureZfsRamdiskDataset)
}

Expand Down
2 changes: 1 addition & 1 deletion sled-agent/src/bootstrap/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub enum StartError {
CreateDdmAdminLocalhostClient(#[source] DdmError),

#[error("Failed to create ZFS ramdisk dataset")]
EnsureZfsRamdiskDataset(#[source] zfs::EnsureFilesystemError),
EnsureZfsRamdiskDataset(#[source] zfs::EnsureDatasetError),

#[error("Failed to list zones")]
ListZones(#[source] zone::AdmError),
Expand Down
6 changes: 3 additions & 3 deletions sled-agent/src/zone_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use illumos_utils::running_zone::ServiceProcess;
use illumos_utils::zfs::CreateSnapshotError;
use illumos_utils::zfs::DestroyDatasetError;
use illumos_utils::zfs::DestroySnapshotError;
use illumos_utils::zfs::EnsureFilesystemError;
use illumos_utils::zfs::EnsureDatasetError;
use illumos_utils::zfs::GetValueError;
use illumos_utils::zfs::ListDatasetsError;
use illumos_utils::zfs::ListSnapshotsError;
Expand Down Expand Up @@ -565,8 +565,8 @@ pub enum BundleError {
#[error("Failed to list ZFS snapshots")]
ListSnapshot(#[from] ListSnapshotsError),

#[error("Failed to ensure ZFS filesystem")]
EnsureFilesystem(#[from] EnsureFilesystemError),
#[error("Failed to ensure ZFS dataset")]
EnsureDataset(#[from] EnsureDatasetError),

#[error("Failed to destroy ZFS dataset")]
DestroyDataset(#[from] DestroyDatasetError),
Expand Down
Loading
Loading