Repository navigation
Add: Allow reusing existing local storage pools #1641
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?
Changes from all commits
e8f19dc
cd315da
1416071
b966b2f
d1a848f
88fdfeb
1d15a2f
bd90b8d
0b0a83f
bb814c0
59ff196
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -63,6 +63,10 @@ type DirectStorage struct { | |
| Path string `yaml:"path"` | ||
| Wipe bool `yaml:"wipe"` | ||
| Encrypt bool `yaml:"encrypt"` | ||
|
|
||
| // Recover reuses an existing local storage pool on the disk. | ||
| // It is only supported for local storage. | ||
| Recover bool `yaml:"recover"` | ||
| } | ||
|
|
||
| // InitNetwork represents the structure of the network config in the preseed yaml. | ||
|
|
@@ -401,6 +405,22 @@ func (p *Preseed) validate(name string, bootstrap bool) error { | |
| directCephCount++ | ||
| } | ||
|
|
||
| for _, disk := range system.Storage.Ceph { | ||
| if disk.Recover { | ||
| return fmt.Errorf("Cannot recover Ceph disks of system %q", system.Name) | ||
| } | ||
| } | ||
|
|
||
| if system.Storage.Local.Recover { | ||
| if system.Storage.Local.Path == "" { | ||
| return fmt.Errorf("Missing the local storage disk path to recover on system %q", system.Name) | ||
| } | ||
|
|
||
| if system.Storage.Local.Wipe { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about not asking to wipe if the user already selected to recover the disk?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The interactive flow already excludes reused disks from the wipe selection. In preseed, both flags can be set explicitly, so I added an error to prevent conflicting actions and unintended data loss. Alternatively, I can make recover take precedence over wipe. |
||
| return fmt.Errorf("Cannot both wipe and recover the local storage disk of system %q", system.Name) | ||
| } | ||
| } | ||
|
|
||
| if system.Storage.Local.Path != "" { | ||
| directLocalCount++ | ||
| } | ||
|
|
@@ -923,12 +943,12 @@ func (p *Preseed) Parse(s *service.Handler, c *initConfig, installedServices map | |
| // Setup directly specified disks for ZFS pool. | ||
| if directLocal.Path != "" { | ||
| if c.bootstrap { | ||
| system.TargetStoragePools = append(system.TargetStoragePools, lxd.DefaultPendingZFSStoragePool(directLocal.Wipe, directLocal.Path)) | ||
| system.TargetStoragePools = append(system.TargetStoragePools, lxd.DefaultPendingZFSStoragePool(directLocal.Wipe, directLocal.Recover, directLocal.Path)) | ||
| if s.Name == peer { | ||
| system.StoragePools = append(system.StoragePools, lxd.DefaultZFSStoragePool()) | ||
| } | ||
| } else { | ||
| system.JoinConfig = append(system.JoinConfig, lxd.DefaultZFSStoragePoolJoinConfig(directLocal.Wipe, directLocal.Path)...) | ||
| system.JoinConfig = append(system.JoinConfig, lxd.DefaultZFSStoragePoolJoinConfig(directLocal.Wipe, directLocal.Recover, directLocal.Path)...) | ||
| } | ||
|
|
||
| directZFSMatches[peer] = directZFSMatches[peer] + 1 | ||
|
|
@@ -1102,12 +1122,12 @@ func (p *Preseed) Parse(s *service.Handler, c *initConfig, installedServices map | |
| if len(matched) > 0 { | ||
| zfsMachines[peer] = true | ||
| if c.bootstrap { | ||
| system.TargetStoragePools = append(system.TargetStoragePools, lxd.DefaultPendingZFSStoragePool(filter.Wipe, service.FormatDiskPath(matched[0]))) | ||
| system.TargetStoragePools = append(system.TargetStoragePools, lxd.DefaultPendingZFSStoragePool(filter.Wipe, false, service.FormatDiskPath(matched[0]))) | ||
| if s.Name == peer { | ||
| system.StoragePools = append(system.StoragePools, lxd.DefaultZFSStoragePool()) | ||
| } | ||
| } else { | ||
| system.JoinConfig = append(system.JoinConfig, lxd.DefaultZFSStoragePoolJoinConfig(filter.Wipe, service.FormatDiskPath(matched[0]))...) | ||
| system.JoinConfig = append(system.JoinConfig, lxd.DefaultZFSStoragePoolJoinConfig(filter.Wipe, false, service.FormatDiskPath(matched[0]))...) | ||
| } | ||
|
|
||
| zfsMatches[filter.Find] = zfsMatches[filter.Find] + 1 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -174,12 +174,17 @@ func (s LXDService) DefaultOVNNetwork(ipv4Gateway string, ipv4Range string, ipv6 | |
|
|
||
| // DefaultPendingZFSStoragePool returns the default local storage configuration when | ||
| // creating a pending pool on a specific cluster member target. | ||
| func (s LXDService) DefaultPendingZFSStoragePool(wipe bool, path string) api.StoragePoolsPost { | ||
| // If recoverSource is set, an existing storage pool on the source is reused instead of creating a new one. | ||
| func (s LXDService) DefaultPendingZFSStoragePool(wipe bool, recoverSource bool, path string) api.StoragePoolsPost { | ||
| cfg := map[string]string{"source": path} | ||
| if wipe { | ||
| cfg["source.wipe"] = strconv.FormatBool(wipe) | ||
| } | ||
|
|
||
| if recoverSource { | ||
| cfg["source.recover"] = strconv.FormatBool(recoverSource) | ||
| } | ||
|
|
||
| return api.StoragePoolsPost{ | ||
| Name: DefaultZFSPool, | ||
| Driver: "zfs", | ||
|
|
@@ -204,14 +209,22 @@ func (s LXDService) DefaultZFSStoragePool() api.StoragePoolsPost { | |
|
|
||
| // DefaultZFSStoragePoolJoinConfig returns the default local storage configuration when | ||
| // joining an existing cluster. | ||
| func (s LXDService) DefaultZFSStoragePoolJoinConfig(wipe bool, path string) []api.ClusterMemberConfigKey { | ||
| // If recoverSource is set, an existing storage pool on the source is reused instead of creating a new one. | ||
| func (s LXDService) DefaultZFSStoragePoolJoinConfig(wipe bool, recoverSource bool, path string) []api.ClusterMemberConfigKey { | ||
| wipeDisk := api.ClusterMemberConfigKey{ | ||
| Entity: "storage-pool", | ||
| Name: DefaultZFSPool, | ||
| Key: "source.wipe", | ||
| Value: "true", | ||
| } | ||
|
|
||
| recoverDisk := api.ClusterMemberConfigKey{ | ||
| Entity: "storage-pool", | ||
| Name: DefaultZFSPool, | ||
| Key: "source.recover", | ||
| Value: "true", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this value need to be based on
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The key is only appended when if recoverSource {
joinConfig = append(joinConfig, recoverDisk)
}So it can never end up in the config as true unless recovery was explicitly requested. This follows the same pattern as the existing wipeDisk key just above it. If the logic is confusing, I'm happy to build the key inside the if block instead to make the conditional behavior more obvious. |
||
| } | ||
|
|
||
| sourceTemplate := api.ClusterMemberConfigKey{ | ||
| Entity: "storage-pool", | ||
| Name: DefaultZFSPool, | ||
|
|
@@ -224,6 +237,10 @@ func (s LXDService) DefaultZFSStoragePoolJoinConfig(wipe bool, path string) []ap | |
| joinConfig = append(joinConfig, wipeDisk) | ||
| } | ||
|
|
||
| if recoverSource { | ||
| joinConfig = append(joinConfig, recoverDisk) | ||
| } | ||
|
|
||
| return joinConfig | ||
| } | ||
|
|
||
|
|
||
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 this path be actually triggered?
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.
yes; DirectStorage is shared by both the local and Ceph entries, so the following configuration parses successfully:
without the check, recover would simply be silently ignored for Ceph disks.
that said, I noticed that encrypt is also silently ignored for local disks today, so silently ignoring unsupported fields would at least be consistent with the existing behavior.
would you prefer to keep the explicit validation error or remove it for consistency with encrypt? alternatively, I could introduce a separate struct for local storage so that recover is only available there.