-
Notifications
You must be signed in to change notification settings - Fork 54
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
Add orange flavor btrfs support #2207
Conversation
rdesaintleger
commented
Oct 17, 2024
- remove btrfs default subvolume,
- save btrfs device in Btrfs structure,
- rework brtfs state mountpoint detection (uses btrfs device, got rid of rootSubvolID and snapshotsSubvolID constants),
- add 'active' relative symlink to active snapshot (updated in CloseTransaction() and read in getActiveSnapshot()),
- add grub 'active_snap' variable (could it be replaced by dracut resolving the 'active' link ?),
- add grub 'root_subpath' variable which allows to prefix kernel and initrd image with a subpath,
- handle grub modules packaging under /usr/lib/elemental/bootloader
- add RelativizeLink() to recursively make symbolic links relative,
- ensure kernel and initrd are relative links (allow grub to process without mounting subvolume)
- update orange Dockerfile
@rdesaintleger thanks for your PR ! |
Sorry, my PR is bad for the moment I found a fix for the RelativizeLink() function which allow build tests to go further (up to snapshotter). Also I didn't notice that snapper did use the btrfs default subvolume feature. I think that I'll make a new snapshotter without snapper named 'btrfs_nosnapper' with its own regression tests. This will have the advantage of not breaking existing btrfs green flavors and being cross distro when using new snapshotter. |
@rdesaintleger really nice to see this implemented! Looking forward to the new snapshotter! Looking back we should probably have named the current "btrfs" snapshotter as "snapper" instead.. oh well! |
At a time when I coded this snapshotter in head there were few configuration options for the btrfs snapshotter. Those were related to the required stack. Pure btrfs implementation, without snapper, implementation with snapper and third option using tukit. So my idea was having like three different levels of abstraction. So, even if the implementation could be in different files or structures I imagined all three as single snapshotter for btrfs. The thing is that snapper also support some sort of LVM snapshots which are definitely not supported by Elemental-toolkit and tukit assumes snapper. Hence in all cases we are talking about BTRFS subvolumes. Just explaining that because most of the parts that are required to make a wrapper for btrfs tool are already implemented in the current snaphsotter hence we should figure out some ways to reuse all that. See the following manual steps I did at a time to make a PoC before actually implementing the snapshotter for btrfs. For install (first snaphsot): # Enable quota management and create subvolumes
btrfs quota enable /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@/.snapshots
mkdir -p /mnt/@/.snapshots/1
btrfs subvolume snapshot /mnt/@ /mnt/@/.snapshots/1/snapshot
# ID should be computed from a "btrfs subvolume list" command
# default set to the equivalent of the STATE partition (probably a /@/state subvolume would make more sense)
volid=$(btrfs subvolume list --sort path /mnt | head -n 1 | cut -d" " -f2)
btrfs subvolume set-default ${volid} /mnt
# Create snapshot 1 info
date=$(date +'%Y-%m-%d %H:%M:%S')
cat << EOF > /mnt/@/.snapshots/1/info.xml
<?xml version="1.0"?>
<snapshot>
<type>single</type>
<num>1</num>
<date>${date}</date>
<description>first root filesystem</description>
</snapshot>
EOF
# Dump the built image into the first snapshot subvolume
./build/elemental pull-image --local ${osimage} /mnt/@/.snapshots/1/snapshot/
# Create the quota group
btrfs qgroup create 1/0 /mnt
#chroot /mnt/@/.snapshots/1/snapshot snapper --no-dbus set-config QGROUP=1/0
# set first snapshot as readonly
btrfs property set /mnt/@/.snapshots/1/snapshot ro true All of the above is already coded in the current snapshotter. So most if not all pieces are already there. I'd be happy to help on getting a proper btrfs wrapper that can be used in both cases. With snapper and without. Probably it is even possible to achieve a snapper compatible setup without snapper. For completeness the rough steps for an upgrade are: # Get current snapshot
current=$(snapper --csvout list --columns number,active | grep yes | cut -d"," -f1)
# Create new snapshot from current
id=$(snapper create --from ${current} --read-write --print-number --description "Snapshot Update of #${current}" --userdata "update-in-progress=yes")
# Apply image to the new snapshot
elemental pull-image --local ${osimage} /mnt/@/.snapshots/${id}/snapshot/
snapper modify --userdata "update-in-progress=no" $id
# Make snapshot RO
btrfs property set /.snapshots/${id}/snapshot ro true See here snapper is only used for the creation of a new subvolume. |
Thanks for information. I'll try the following (not sure yet for the grub part)
If grub is unable to test for snapshot directory I'll search for another solution. The property name comes from the original problem which is that other distro's grub do not handle properly default subvolumes. |
Now able to boot orange on btrfs. elemental upgrade works in recovery but not in active state due to snapper configuration |
67fbaa4
to
b19d09f
Compare
I'm actually near the end of snapshotter modifications. I've added two booleans for configuration
The real needed change for orange to boot with own bootloader is the grub variable (since 'set btrfs_relative_path="y"' has no effect at all). I've managed to get snapper running with Ubuntu (However sometimes it takes time to enable snapper on first boot, making elemental upgrade fail until fully snapperd is fully bootstraped). I've done the following changes for snapper bootstrap (it's not actually real shell code) :
This process is compatible at least with orange and green flavor. It's heavier than the previous process but I find it more proof to distro specific file path for templates (root snapper config was not created in Ubuntu). What do you think about it ? Have you got any advice before I work to fix test-cli make target ? I may need help to fix tests which need chrooted calls. |
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 process is compatible at least with orange and green flavor. It's heavier than the previous process but I find it more proof to distro specific file path for templates (root snapper config was not created in Ubuntu).
I see snapper lacking a root config might be challenging but I am not sure I am comfortable with this bootstrap approach, really looks like an overkill and the logic to mimic snapper was almost entirely there.
IMHO snapper needs openSUSE/snapper#944 which is already in the process of being implemented upstream, when done this would not be needed anymore. Hence I'd be more in favor of the former approach to mimic snapper with btrfs. This could also eventually lead us to a position in which the btrfs only snapshotter could be compatible with snapper, so it could be even possible to migrate from btrfs only to snapper at some point as soon as the underlying distro supports the required version of snapper.
Your PR entirely rewrites the btrfs snapshotter, this is a sensitive portion of code. It might take some iterations to get that done. To be frank I am a bit unsure how to proceed. I'd probably try to iterate in smaller steps, I'll try to provide some specific ideas in the following days.
pkg/action/init.go
Outdated
return err | ||
} | ||
|
||
err = utils.RelativizeLink(cfg.Fs, constants.InitrdPath) |
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 struggle to see the need of this step and the need of RelativizeLink method in general. Why would we delete and recreate a symlink we just created a couple of lines above?
I'd say something like:
relInitrd, err := filepath.Rel(filepath.Dir(constants.InitrdPath), initrd)
if err != nil {
cfg.Config.Logger.Errorf("could set a relative path from '%s' to '%s': %v", constants.InitrdPath, initrd, err)
return err
}
err = cfg.Fs.Symlink(relInitrd, constants.InitrdPath)
Should be sufficient and entirely omit the need of RelativizeLink
method.
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.
To be useable in Grub without default subvolumes kernel and initrd links must be recursively relative. I initially set a single link like you suggest with duplicated code on kernel and initrd. However I wanted to ensure that if target of initial link is itself a link, it was a relative link too. Making a general purpose function to relativize recursively a link sounded a better idea since it also simplify the code (don't touch actual code, but adds a call to relativize).
I'll test with requested change (without relativizing) and I'll let you know
pkg/utils/fs.go
Outdated
@@ -243,6 +243,65 @@ func (d *statDirEntry) IsDir() bool { return d.info.IsDir() } | |||
func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() } | |||
func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil } | |||
|
|||
// RelativizeLink takes a file path, resolves symlinks recursively, | |||
// and relativizes the target against the link directory. | |||
func RelativizeLink(fs types.FS, path string) error { |
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 think this entire method is needed
5258f16
to
4c51fc9
Compare
Ok, I've restarted from scratch using btrfs.go from main branch. The following changes have been done outside the main feature:
for the cross distro btrfs snapshotter:
|
@rdesaintleger would you mind to hold the implementation for a day or two? Sorry I have been working on it for a while and I think I managed to divide the snapper and pure btrfs snapshotter without touching former logic (aka keep using static volume IDs, current grub and no changes in unit and e2e tests). I will include today eve a draft PR of it. Then if all looks good I wonder if we can start to tacke one by one all the other missing refactors (grub, default volume issues, etc.). I hope this is OK for you, thanks much for your contributions |
@rdesaintleger I created this PR #2220 which essentially enables pure btrfs snapshotter but keeping almost 100% of the previous logic when using snapper. The pure btrfs implementation follows the same pattern as done by snapper, I don't have any good argument not to do so and this also potentially enables the possibility of start using snapper later in time. |
Fine, thank you @davidcassany Just to be clear before starting over. Do you want me to make a PR (with modifications committed one by one) on your new repo before dropping this one ? Also, I've setup two booleans for two features, do you want me to dropthe active snapshot symlink feature (to avoid using btrfs incompat features) ? Lastly : I've used 'disable-snapper' to be sure that the default behavior of the snapshotter would be to use snapper to support green flavor downgrades (As I did not find out how to use 'true' as default value). Do I restart with your definition or do I use 'disable-snapper' ? |
@rdesaintleger hi sorry for the delay, had some time off these days.
So, my expectation is that now from the current main branch it should be possible to make few little changes to improve the cross distro capabilities. So I think one of the most prominent needed changes is around grub configuration and work around the missing Regarding the disable default subvolume feature, am not sure about it, I don't see the need of it. If this is just a grub2 matter then I'd say this needs to be fixed in grub config. Is this causing issues in Ubuntu at runtime? default subvolume concept I don't think is a SUSE specific addition on top of btrfs. Finally you noted some improvements on some methods, thanks much for that, I'd be happy to review and merge those on small PRs (probably not necessarily one per PR as long as we don't have hundreds of lines together). |
Changes: - ensure that kernel and initrd are relative links - add 'active_snap' variable to grub (managed by snapshotter) - add 'root_subpath' variable to grub - snapper can now be used on orange flavor (see notes) Notes: - 'active_snap' and 'root_subpath' allows grub to build a relative path to the kernel and initrd when btrfs_relative_path is not available. - Snapper works on orange flavor, however it can take several minutes before the daemon initialize in active or passive mode. If elemental upgrade is invoked during this time it will fail.
btrfs volume IDs are not accurate with newer btrfs formated volumes. They are now matched using their names. Changes: - added '-a' to subvolume list to encure that full subvolume path is available - changed btrfs probe from ID match to name match - changed state volume match in findStateMount from target mount point to source subvolume
4c51fc9
to
2f7e7c1
Compare
Changed my branch to include your changes. Basically I did 2 commits. The first one have just needed modifications to enable btrfs snapshotter on orange flavor (I had also booted a debian trixie too but snapshots and state got mounted read only). The second commit fix btrfs state volumes detection (258 is the first snapshot ID which will get deleted and will trigger a bug where btrfs state volume will be seen as uninitialized). I dropped the active symlink feature as suggested. The first commit basically do the following:
I've disabled snapper in orange flavor since it takes time to initialize on boot. It however works. The setBootloader() now takes the commited snapshotID as argument. I guess that this ID could be propagated to other calls but I did not investigate further to keep a minimal impact (could be propagated to getPassiveSnapshots() and an internal version of GetSnapshots()). The second commit changes the btrfs backend Probe() and findStateMount(). Basically subvolumes are listed with '-a' to ensure that the whole subvolume name is available (<FS_TREE>/ is stripped out from names). A new function getStateSubvolumes() returns root and snapshots subvolumes structure by matching their names (this function is used by Probe()). Lastly the findStateMount() has been modified to extract subvolume name from findmnt command. It's done using substring index/lastIndex to avoid wrong regex matches if subvolumes are named using '[' or ']'. This allow to match state mountpoint using the source subvolume and not the target mount. Could not figure out how to launch workflow in my repo, sorry if errors are still showing up. Did manual testing on green and orange flavors. I can discard the last commit and make a separate Issue/PR if you wish. |
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.
Looks good to me 👍 Thanks much for your contribution and patience
@@ -265,8 +263,9 @@ func (b *Btrfs) CloseTransaction(snapshot *types.Snapshot) (err error) { | |||
return err | |||
} | |||
|
|||
_ = b.setBootloader() | |||
// cleanup snapshots before setting bootloader otherwise deleted snapshots may show up in bootloader |
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.
Good catch, thanks
snapperSysconfig = "/etc/sysconfig/snapper" | ||
snapperRootConfig = "/etc/snapper/configs/root" | ||
snapperSysconfig = "/etc/sysconfig/snapper" | ||
snapperDefaultconfig = "/etc/default/snapper" |
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.
Good catch, I was not aware that snapper on Ubuntu uses different configuration paths
@@ -411,14 +398,36 @@ func (b btrfsBackend) findSubvolumeByPath(rootDir, path string) (int, error) { | |||
|
|||
// getSubvolumes lists all btrfs subvolumes for the given root | |||
func (b btrfsBackend) getSubvolumes(rootDir string) (btrfsSubvolList, error) { | |||
out, err := b.cfg.Runner.Run("btrfs", "subvolume", "list", "--sort=path", rootDir) | |||
out, err := b.cfg.Runner.Run("btrfs", "subvolume", "list", "-a", "--sort=path", rootDir) |
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, this is a nice improvement, thanks 👍
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