Skip to content

Conversation

@smklein
Copy link
Collaborator

@smklein smklein commented Apr 8, 2025

Related to #7887 - we have seen that datasets nested within
encrypted datasets are not automatically mounted on reboot.

To mitigate this issue: whenever we perform an operation which attempts to read the dataset, ensure
that it is mounted. To do this operation, some minor refactors were made to mounting-related code.

Fixes #7884

@smklein smklein changed the title [sled-agent] Ensure Support Bundles are mounted [sled-agent] Ensure Support Bundles datasets are mounted Apr 8, 2025
#[derive(Debug, Clone)]
pub enum Mountpoint {
#[allow(dead_code)]
Legacy,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It simplifies logic elsewhere if this is "always a path". I went ahead and updated this, since there appear to be no users of "legacy".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dataset mounted at "/" will actually show up as a legacy mountpoint, however I did a quick spot check and it doesn't look like we use this anywhere when reading from zfs to get a mountpoint.

}

impl CanMount {
fn wants_mounting(&self) -> bool {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just to make the code more readable below. Should be the same functionality as before.

return Ok(result);
}

struct DatasetMountInfo {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We used to return a tuple of bools, I'm just making this explicit for readability.

// Returns a dataset that the sled has been explicitly configured to use.
async fn get_configured_dataset(
//
// Returns an error if this dataset is not mounted.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used to access the "parent dataset" on which the "support bundle dataset" gets placed.

E.g., "Look up the crypt/debug dataset by UUIDs".

I'm adding these checks to also validate: ALSO confirm that this dataset is mounted.

This is tested in cannot_create_bundle_on_unmounted_parent

let support_bundle_path = dataset
.name
.mountpoint(&self.storage.zpool_mountpoint_root())
.ensure_mounted_and_get_mountpoint(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every spot where we read the mountpoint of the support bundle dataset: Instead, ensure it's actually mounted first.

This is tested in listing_bundles_mounts_them


// The mounted root of the support bundle dataset
let support_bundle_dir = dataset
.ensure_mounted_and_get_mountpoint(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This technically is not tested - the call to dyn_nested_dataset_ensure above should actually do the mounting, so this call shouldn't actually be necessary. However, I included it here out of consistency with the other two callsites.

(This isn't easy to test; I'd basically need to stop this function from running halfway through. But it seems like a "paranoid check" anyway).

let support_bundle_dir =
dataset.mountpoint(&self.storage.zpool_mountpoint_root());
let support_bundle_dir = dataset
.ensure_mounted_and_get_mountpoint(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tested in getting_bundles_mounts_them.

#[derive(Debug, Clone)]
pub enum Mountpoint {
#[allow(dead_code)]
Legacy,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dataset mounted at "/" will actually show up as a legacy mountpoint, however I did a quick spot check and it doesn't look like we use this anywhere when reading from zfs to get a mountpoint.

}
if wants_mounting {
let path = &mountpoint.0;
ensure_empty_immutable_mountpoint(&path).map_err(|err| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be ensure_dataset_mounted()?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is on the creation path. The dataset does not exist yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, the "wants_mounting" is a little confusing but I understand now. Maybe add a comment that mentions creating the immutable mountpoint for future mounting.

Copy link
Collaborator Author

@smklein smklein Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I have a comment on lines 1012 - 1019 which tries to cover this:

        // Non-zoned datasets with an explicit mountpoint and the
        // "canmount=on" property should be mounted within the global zone.
        //
        // We'll ensure they have an empty immutable mountpoint before
        // creating the dataset itself, which will also mount it.
        //
        // Zoned datasets are mounted when their zones are booted, so
        // we don't do this mountpoint manipulation for them.

Do you have recommendations for how to make this more clear? My read of this is that it already does mention "creating the immutable mountpoint for future mounting", in particular:

        // We'll ensure they have an empty immutable mountpoint before
        // creating the dataset itself, which will also mount it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I can re-shuffle the comment to go alongside the wants_mounting variable.


async fn is_mounted(dataset: &str) -> bool {
let mut command = tokio::process::Command::new(illumos_utils::zfs::ZFS);
let cmd = command.args(&["list", "-Hpo", "mounted", dataset]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really necessary and I don't know how much care about this function being async, but you could call illumos_utils::zfs::Zfs::get_value(dataset, "mounted"), which will return a String in the Ok path.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a module-local test-only function, I don't have a strong preference tbh. Do you care strongly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't, feel free to skip it.

}),
)
.await
.expect_err("Should not have been able to create support bundle");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this test!

}
if wants_mounting {
let path = &mountpoint.0;
ensure_empty_immutable_mountpoint(&path).map_err(|err| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, the "wants_mounting" is a little confusing but I understand now. Maybe add a comment that mentions creating the immutable mountpoint for future mounting.


async fn is_mounted(dataset: &str) -> bool {
let mut command = tokio::process::Command::new(illumos_utils::zfs::ZFS);
let cmd = command.args(&["list", "-Hpo", "mounted", dataset]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't, feel free to skip it.

Copy link
Contributor

@wfchandler wfchandler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for fixing this!

@smklein smklein enabled auto-merge (squash) April 10, 2025 17:12
@smklein smklein merged commit 1a6317c into main Apr 10, 2025
16 checks passed
@smklein smklein deleted the support-bundle-mounting branch April 10, 2025 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[support bundles] Confirm that support bundle datasets can be mounted on reboot

4 participants