-
Notifications
You must be signed in to change notification settings - Fork 66
Use an unencrypted dataset for local storage #9684
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
base: main
Are you sure you want to change the base?
Use an unencrypted dataset for local storage #9684
Conversation
Using a dataset that inherits encryption for raw zvols isn't possible, so create a new unencrypted parent dataset for local storage, and switch over to using that. Note that there isn't a migration plan for existing disks backed by local storage. The sled-agent API simply switches over to using the unencrypted dataset instead, and Nexus' allocation routines similarly switch over. All existing disks backed by local storage should be deleted before this PR lands!
| /// Delegate a slice of the local storage dataset present on this pool into | ||
| /// the zone. | ||
| /// Delegate a slice of the unencrypted local storage dataset present on | ||
| /// this pool into the zone. |
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.
Is it okay to change the meaning of this variant as opposed to adding a LocalStorageUnencrypted variant to match the two dataset kinds? I'm wondering if this will be a pain when we get back to DatasetKind::LocalStorage (encrypted) support.
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.
Putting both encrypted and unencrypted variants in here required an API rev because DelegatedZvol is passed through InstanceSledLocalConfig in the InstanceEnsureBody, and this turned into more support for both encrypted and unencrypted local storage datasets, in particular deleting now works for both. I have the changes staged in my branch but would really like oxidecomputer/dropshot#1520 to land (and be pulled in here!) so that the resulting code isn't too duplicative.
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.
done in ad79d19
| (Some(_), Some(_)) => { | ||
| return Err(Error::internal_error(&format!( | ||
| "LocalStorageDisk {} has multiple allocations!", | ||
| self.id(), | ||
| ))); | ||
| } |
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.
Can we encode this requirement in the type system? E.g., instead of two Option fields, a single field with a enum with variants spelling out the valid options?
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.
done in dc51a95 - I should have done this in the first place, the code is much nicer.
| format!("{zpool_name}/crypt/local_storage"), | ||
| format!("{dataset_id}/vol"), | ||
| ] | ||
| .join("/"); |
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 realize this was already here but I guess I missed in the last PR. It seems very fishy for Nexus to be building up paths to devices on sleds - can this level of detail be left to sled-agent, and Nexus deal with higher level things (like just the zpool / dataset IDs)?
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 function is currently only consumed by Nexus when building the instance spec to send to sled-agent. I'm mildly uncomfortable with the idea that sled-agent has to reach into the prepared spec to edit something, though we could introduce an abstraction in the form of a Nexus-level instance spec that gets converted by the sled-agent into a proper Propolis instance spec, and that would allow Nexus to deal only in the higher level UUIDs.
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.
Fair enough; if this is gross/painful either way and it's better to leave it as-is, that's fine. I've been trying to be a little more critical of how we treat paths/strings in sled-agent, and was quite surprised to see this in Nexus. Might be worth a discussion after R18 is out the door?
| local_storage_dataset_allocation, | ||
| local_storage_unencrypted_dataset_allocation, |
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 looks like this function can return Ok(_) with both of these fields set to Some(_) or None(_), which will end up causing errors above. I feel more strongly this should be a single field, now, but if for some reason it can't, should this function check that exactly one of these fields is Some(_)?
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.
right, also done in dc51a95
|
|
||
| /// List one page of unencrypted local storage datasets | ||
| /// | ||
| /// This fetches all datasets, including those that have been tombstoned. |
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.
Including tombstoned datasets seems unusual for a _list function - should we make the name scarier sounding so it's more obvious to callers that they may need to consider each item's status?
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 was basically all copied from the existing rendezvous code, which similarly returns tombstoned datasets - hopefully callers would be aware of this convention already! :)
If not then yeah we'll need to establish a naming convention for "also returns deleted and/or tombstoned stuff".
| { | ||
| info!(&log, "disk.local_storage_dataset_allocation.is_some()"); | ||
| return Err(SledReservationTransactionError::Reservation( | ||
| SledReservationError::NotFound, |
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 error indicate that we can't reserve this disk because there's an older local storage dataset that needs to be cleaned up?
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.
yeah, dc51a95 changed this to "disk has an unsupported encrypted dataset allocation"
| let mut stats = DatasetsRendezvousStats::default(); | ||
|
|
||
| for bp_dataset in blueprint_datasets { | ||
| // Filter down to LocalStorage datasets... |
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.
| // Filter down to LocalStorage datasets... | |
| // Filter down to LocalStorageUnencrypted datasets... |
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.
fixed too
| for bp_dataset in blueprint_datasets { | ||
| // Filter down to LocalStorage datasets... | ||
| let dataset = match (&bp_dataset.kind, bp_dataset.address) { | ||
| (DatasetKind::LocalStorageUnencrypted, None) => { |
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.
Why do we match on an address of None here?
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.
my mental model of this is that the only datasets with addresses are the Crucible ones, maybe that's wrong?
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, that's not wrong, but if we have an explicit DatasetKind::LocalStorageUnencrypted, isn't address guaranteed to always be None anyway?
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'd hope so haha - I'm inclined to leave the None match in as a double-check for this guarantee though.
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 we have a CHECK constraint that guarantees the address is only set for crucible datasets? On the Rust side it'd be awfully nice if we modeled this differently, but that gets into the whole DatasetName / DatasetKind redesign thing :(
My concern with matching on the address here is that it gives the appearance that we could have DatasetKind::LocalStorageUnencrypted datasets with an address, and that we're intentionally choosing to ignore those. I'd drop that part of the match for that reason alone, personally, but it's not a hill I'm willing to die on.
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.
Thinking about it for a bit, I agree with you about dropping the match on address: in the future if we have addresses here, we'll still want the rendezvous tables management to work, or we'll have changed these types and it won't matter. Dropped in 849942e
| ); | ||
|
|
||
| format!("{}/{}", local_storage_parent.full_name(), dataset_id) | ||
| } |
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.
Small style nit - for matches like this, I think it'd be clearer to try to shrink them down to only the bits that are distinct. In this case, it looks like the only change is the DatasetKind we pass to DatasetName::new()? So maybe with some helper methods like zpool_id() for common fields this could be
let dataset_kind = match self {
DelegatedZvol::LocalStorageUnencrypted { .. } => DatasetKind::LocalStorageUnencrypted,
DelegatedZvol::LocalStorageEncrypted { .. } => DatasetKind::LocalStorage,
};
let local_storage_parent = DatasetName::new(
ZpoolName::External(self.zpool_id()),
dataset_kind,
);
format!("{}/{}", local_storage_parent.full_name(), self.dataset_id())or even make a dataset_kind() method that does the match internally, making this
let local_storage_parent = DatasetName::new(
ZpoolName::External(self.zpool_id()),
self.dataset_kind(),
);
format!("{}/{}", local_storage_parent.full_name(), self.dataset_id())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.
shrunk in 4ce5bc2
| for bp_dataset in blueprint_datasets { | ||
| // Filter down to LocalStorage datasets... | ||
| let dataset = match (&bp_dataset.kind, bp_dataset.address) { | ||
| (DatasetKind::LocalStorageUnencrypted, None) => { |
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 we have a CHECK constraint that guarantees the address is only set for crucible datasets? On the Rust side it'd be awfully nice if we modeled this differently, but that gets into the whole DatasetName / DatasetKind redesign thing :(
My concern with matching on the address here is that it gives the appearance that we could have DatasetKind::LocalStorageUnencrypted datasets with an address, and that we're intentionally choosing to ignore those. I'd drop that part of the match for that reason alone, personally, but it's not a hill I'm willing to die on.
nexus/src/app/sagas/disk_delete.rs
Outdated
| let dataset_id = allocation.id(); | ||
| let pool_id = allocation.pool_id(); | ||
| let sled_id = allocation.sled_id(); | ||
| let (sled_id, request) = match allocation { |
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.
Similar style nit as the match above - could we shrink the match down to just the encrypted_at_rest field?
let encrypted_at_rest = match allocation {
datastore::LocalStorageAllocation::Unencrypted(_) => false,
datastore::LocalStorageAllocation::Encrypted(_) => true,
};
let sled_id = allocation.sled_id();
let request = LocalStorageDatasetDeleteRequest {
zpool_id: allocation.pool_id(),
dataset_id: allocation.id(),
encrypted_at_rest,
};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.
also 4ce5bc2
Using a dataset that inherits encryption for raw zvols isn't possible, so create a new unencrypted parent dataset for local storage, and switch over to using that.
Note that there isn't a migration plan for existing disks backed by local storage. The sled-agent API simply switches over to using the unencrypted dataset instead, and Nexus' allocation routines similarly switch over. All existing disks backed by local storage should be deleted before this PR lands!