Skip to content
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

Merged
merged 2 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion examples/orange/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
locales \
kbd \
podman \
xz-utils
btrfs-progs \
btrfsmaintenance \
xz-utils && \
apt-get clean && rm -rf /var/lib/apt/lists/*

# Hack to prevent systemd-firstboot failures while setting keymap, this is known
# Debian issue (T_T) https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=790955
Expand All @@ -75,6 +78,9 @@ RUN cp /usr/share/systemd/tmp.mount /etc/systemd/system
# the default cloud-init
RUN locale-gen --lang en_US.UTF-8

# Add default snapshotter setup
ADD snapshotter.yaml /etc/elemental/config.d/snapshotter.yaml

# Generate initrd with required elemental services
RUN elemental --debug init -f

Expand Down
5 changes: 5 additions & 0 deletions examples/orange/snapshotter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
snapshotter:
type: btrfs
max-snaps: 4
config:
snapper: false
18 changes: 15 additions & 3 deletions pkg/action/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package action

import (
"fmt"
"path/filepath"
"strings"

"github.com/rancher/elemental-toolkit/v2/pkg/constants"
Expand Down Expand Up @@ -69,7 +70,12 @@ func RunInit(cfg *types.RunConfig, spec *types.InitSpec) error {
if kernel != constants.KernelPath {
cfg.Config.Logger.Debugf("Creating kernel symlink from %s to %s", kernel, constants.KernelPath)
_ = cfg.Fs.Remove(constants.KernelPath)
err = cfg.Fs.Symlink(kernel, constants.KernelPath)
relKernel, err := filepath.Rel(filepath.Dir(constants.KernelPath), kernel)
if err != nil {
cfg.Config.Logger.Errorf("could set a relative path from '%s' to '%s': %v", constants.KernelPath, kernel, err)
return err
}
err = cfg.Fs.Symlink(relKernel, constants.KernelPath)
if err != nil {
cfg.Config.Logger.Errorf("failed creating kernel symlink")
return err
Expand All @@ -89,7 +95,7 @@ func RunInit(cfg *types.RunConfig, spec *types.InitSpec) error {
cfg.Config.Logger.Errorf("dracut failed with output: %s", output)
}

cfg.Config.Logger.Debugf("darcut output: %s", output)
cfg.Config.Logger.Debugf("dracut output: %s", output)

initrd, err := utils.FindInitrd(cfg.Fs, "/")
if err != nil || !strings.HasPrefix(initrd, constants.ElementalInitrd) {
Expand All @@ -99,9 +105,15 @@ func RunInit(cfg *types.RunConfig, spec *types.InitSpec) error {

cfg.Config.Logger.Debugf("Creating initrd symlink from %s to %s", initrd, constants.InitrdPath)
_ = cfg.Fs.Remove(constants.InitrdPath)
err = cfg.Fs.Symlink(initrd, constants.InitrdPath)
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)
if err != nil {
cfg.Config.Logger.Errorf("failed creating initrd symlink")
return err
}

return err
Expand Down
1 change: 1 addition & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const (
GrubDefEntry = "Elemental"
GrubFallback = "default_fallback"
GrubPassiveSnapshots = "passive_snaps"
GrubActiveSnapshot = "active_snap"
ElementalBootloaderBin = "/usr/lib/elemental/bootloader"

// Mountpoints or links to images and partitions
Expand Down
31 changes: 23 additions & 8 deletions pkg/features/embedded/grub-config/etc/elemental/grub.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -65,30 +65,45 @@ function set_loopdevice {

## Sources bootargs from the current volume
function source_bootargs {
source (${volume})/etc/cos/bootargs.cfg
source (${volume})/etc/elemental/bootargs.cfg
source (${volume})/${root_subpath}etc/cos/bootargs.cfg
source (${volume})/${root_subpath}etc/elemental/bootargs.cfg
}

## Defines the volume and image to boot from for active or passive boots
function set_volume {
if [ "${snapshotter}" == "btrfs" ]; then
# apply btrfs default subvolume if applicable
set btrfs_relative_path="y"
set volume="${root}"
if [ -n "${1}" ]; then
set img="@/.snapshots/${1}/snapshot"
btrfs-mount-subvol ($root) / ${img}
# check if active snap is defined with default top level volume
if [ -d "@/.snapshots/${active_snap}/snapshot" ]; then
if [ -n "${1}" ]; then
set img="@/.snapshots/${1}/snapshot"
else
set img="@/.snapshots/${active_snap}/snapshot"
fi
set root_subpath="${img}/"
else
# if not in top level use subvolume based mounts
set root_subpath=""
if [ -n "${1}" ]; then
set img="@/.snapshots/${1}/snapshot"
btrfs-mount-subvol ($root) / ${img}
fi
fi
elif [ -z "${1}" ]; then
set root_subpath=""
set_loopdevice /.snapshots/active
else
set root_subpath=""
set img="/.snapshots/${1}/snapshot.img"
set_loopdevice ${img}
fi
}

menuentry "${display_name}" --id active {
set mode=active
search --no-floppy --label --set=root ${state_label}
search --no-floppy --set root --label ${state_label}
set_volume
source_bootargs
linux (${volume})${kernel} ${kernelcmd} ${extra_cmdline} ${extra_active_cmdline}
Expand All @@ -98,7 +113,7 @@ menuentry "${display_name}" --id active {
for passive_snap in ${passive_snaps}; do
menuentry "${display_name} (snapshot ${passive_snap})" --id passive${passive_snap} ${passive_snap} {
set mode=passive
search --no-floppy --label --set=root ${state_label}
search --no-floppy --set root --label ${state_label}
set_volume ${2}
source_bootargs
linux (${volume})${kernel} ${kernelcmd} ${extra_cmdline} ${extra_passive_cmdline}
Expand All @@ -108,7 +123,7 @@ done

menuentry "${display_name} recovery" --id recovery {
set mode=recovery
search --no-floppy --label --set=root ${recovery_label}
search --no-floppy --set root --label ${recovery_label}

# Check the presence of the image and fallback to legacy path if not present
set img=/boot/recovery.img
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ else
set kernelcmd="console=tty1 console=ttyS0 root=LABEL=${state_label} ${img_arg} ${snap_arg} elemental.mode=${mode} elemental.oemlabel=${oem_label} panic=5 security=selinux fsck.mode=force fsck.repair=yes"
fi

set kernel=/boot/vmlinuz
set initramfs=/boot/initrd
set kernel=/${root_subpath}boot/vmlinuz
set initramfs=/${root_subpath}boot/initrd
61 changes: 39 additions & 22 deletions pkg/snapshotter/btrfs-backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,27 +119,14 @@ func newBtrfsBackend(cfg *types.Config, maxSnapshots int) *btrfsBackend {

// Probe tests the given device and returns the found state as a backendStat struct
func (b *btrfsBackend) Probe(device string, mountpoint string) (backendStat, error) {
var rootVolume, snapshotsVolume bool
var stat backendStat

volumes, err := b.getSubvolumes(mountpoint)
rootVolume, snapshotsVolume, err := b.getStateSubvolumes(mountpoint)
if err != nil {
return stat, err
}

b.cfg.Logger.Debugf(
"Looking for subvolume ids %d and %d in subvolume list: %v",
rootSubvolID, snapshotsSubvolID, volumes,
)
for _, vol := range volumes {
if vol.id == rootSubvolID {
rootVolume = true
} else if vol.id == snapshotsSubvolID {
snapshotsVolume = true
}
}

if rootVolume && snapshotsVolume {
if (rootVolume != nil) && (snapshotsVolume != nil) {
id, err := b.getActiveSnapshot(mountpoint)
if err != nil {
return stat, err
Expand Down Expand Up @@ -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)
Copy link
Contributor

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 👍

if err != nil {
b.cfg.Logger.Errorf("failed listing btrfs subvolumes: %s", err.Error())
return nil, err
}
return parseVolumes(strings.TrimSpace(string(out))), nil
}

func (b btrfsBackend) getStateSubvolumes(rootDir string) (rootVolume *btrfsSubvol, snapshotsVolume *btrfsSubvol, err error) {
volumes, err := b.getSubvolumes(rootDir)
if err != nil {
return nil, nil, err
}

snapshots := filepath.Join(rootSubvol, snapshotsPath)
b.cfg.Logger.Debugf(
"Looking for subvolumes %s and %s in subvolume list: %v",
rootSubvol, snapshots, volumes,
)
for _, vol := range volumes {
if vol.path == rootSubvol {
rootVolume = &vol
} else if vol.path == snapshots {
snapshotsVolume = &vol
}
}

return rootVolume, snapshotsVolume, err
}

// getActiveSnapshot returns the active snapshot. Zero value means there is no active or default snapshot
func (b btrfsBackend) getActiveSnapshot(rootDir string) (int, error) {
out, err := b.cfg.Runner.Run("btrfs", "subvolume", "get-default", rootDir)
Expand Down Expand Up @@ -448,7 +457,7 @@ func parseVolumes(rawBtrfsList string) btrfsSubvolList {
match := re.FindStringSubmatch(strings.TrimSpace(scanner.Text()))
if match != nil {
id, _ := strconv.Atoi(match[1])
path := match[2]
path := strings.TrimPrefix(match[2], "<FS_TREE>/")
list = append(list, btrfsSubvol{id: id, path: path})
}
}
Expand Down Expand Up @@ -530,11 +539,19 @@ func (b btrfsBackend) findStateMount(device string) (rootDir string, stateMount
if len(lineFields) != 2 {
continue
}
if strings.Contains(lineFields[1], constants.RunningStateDir) {
stateMount = lineFields[1]
} else if match := r.FindStringSubmatch(lineFields[0]); match != nil {
rootDir = lineFields[1]
snapshotID, _ = strconv.Atoi(match[1])

subStart := strings.Index(lineFields[0], "[/")
subEnd := strings.LastIndex(lineFields[0], "]")

if subStart != -1 && subEnd != -1 {
subVolume := lineFields[0][subStart+2 : subEnd]

if subVolume == rootSubvol {
stateMount = lineFields[1]
} else if match := r.FindStringSubmatch(subVolume); match != nil {
rootDir = lineFields[1]
snapshotID, _ = strconv.Atoi(match[1])
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/snapshotter/btrfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ import (

const (
rootSubvol = "@"
rootSubvolID = 257
snapshotsSubvolID = 258
snapshotsPath = ".snapshots"
snapshotPathTmpl = ".snapshots/%d/snapshot"
snapshotPathRegex = `.snapshots/(\d+)/snapshot`
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch, thanks

_ = b.backend.SnapshotsCleanup(b.rootDir)
_ = b.setBootloader(snapshot.ID)
return nil
}

Expand Down Expand Up @@ -379,7 +378,7 @@ func (b *Btrfs) getPassiveSnapshots() ([]int, error) {
}

// setBootloader sets the bootloader variables to update new passives
func (b *Btrfs) setBootloader() error {
func (b *Btrfs) setBootloader(activeSnapshotID int) error {
var passives, fallbacks []string

b.cfg.Logger.Infof("Setting bootloader with current passive snapshots")
Expand All @@ -404,6 +403,7 @@ func (b *Btrfs) setBootloader() error {
envs := map[string]string{
constants.GrubFallback: fallbackList,
constants.GrubPassiveSnapshots: snapsList,
constants.GrubActiveSnapshot: strconv.Itoa(activeSnapshotID),
"snapshotter": constants.BtrfsSnapshotterType,
}

Expand Down
11 changes: 8 additions & 3 deletions pkg/snapshotter/snapper-backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ import (
)

const (
snapperRootConfig = "/etc/snapper/configs/root"
snapperSysconfig = "/etc/sysconfig/snapper"
snapperRootConfig = "/etc/snapper/configs/root"
snapperSysconfig = "/etc/sysconfig/snapper"
snapperDefaultconfig = "/etc/default/snapper"
Copy link
Contributor

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

)

var _ subvolumeBackend = (*snapperBackend)(nil)
Expand Down Expand Up @@ -220,7 +221,11 @@ func (s snapperBackend) configureSnapper(snapshotPath string) error {
}

sysconfigData := map[string]string{}
sysconfig := filepath.Join(snapshotPath, snapperSysconfig)
sysconfig := filepath.Join(snapshotPath, snapperDefaultconfig)
if ok, _ := utils.Exists(s.cfg.Fs, sysconfig); !ok {
sysconfig = filepath.Join(snapshotPath, snapperSysconfig)
}

if ok, _ := utils.Exists(s.cfg.Fs, sysconfig); ok {
sysconfigData, err = utils.LoadEnvFile(s.cfg.Fs, sysconfig)
if err != nil {
Expand Down
Loading