-
Notifications
You must be signed in to change notification settings - Fork 62
[sled-agent] Ensure Support Bundles datasets are mounted #7938
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
Conversation
| #[derive(Debug, Clone)] | ||
| pub enum Mountpoint { | ||
| #[allow(dead_code)] | ||
| Legacy, |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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| { |
There was a problem hiding this comment.
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()?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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| { |
There was a problem hiding this comment.
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]); |
There was a problem hiding this comment.
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.
wfchandler
left a comment
There was a problem hiding this 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!
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