Skip to content

Commit

Permalink
Make snapshotter root dynamic based on the reported mountpoint (#1971)
Browse files Browse the repository at this point in the history
* Make snapshotter root dynamic based on the reported mountpoint
* Add state partition as a InitSnapshotter parameter
* Fix build-disk command and unit tests

Signed-off-by: David Cassany <dcassany@suse.com>
  • Loading branch information
davidcassany authored Feb 26, 2024
1 parent e7630b0 commit e04b8c2
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 198 deletions.
223 changes: 137 additions & 86 deletions pkg/action/build-disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@ func NewBuildDiskAction(cfg *v1.BuildConfig, spec *v1.DiskSpec, opts ...BuildDis
}

if b.cfg.Snapshotter.Type == constants.BtrfsSnapshotterType {
if !b.spec.Expandable {
cfg.Logger.Errorf("Non expandable disk images are not supported for btrfs snapshotter")
return nil, fmt.Errorf("Not supported")
}
if spec.Partitions.State.FS != constants.Btrfs {
cfg.Logger.Warning("Btrfs snapshotter type, forcing btrfs filesystem on state partition")
spec.Partitions.State.FS = constants.Btrfs
Expand Down Expand Up @@ -257,52 +253,6 @@ func (b *BuildDiskAction) BuildDiskRun() (err error) { //nolint:gocyclo
b.cfg.Logger.Errorf("failed creating expandable cloud-config: %s", err.Error())
return err
}
} else {
// Run a snapshotter transaction for System source in state partition
err = b.snapshotter.InitSnapshotter(b.roots[constants.StatePartName])
if err != nil {
b.cfg.Logger.Errorf("failed initializing snapshotter")
return elementalError.NewFromError(err, elementalError.SnapshotterInit)
}
// Starting snapshotter transaction
b.cfg.Logger.Info("Starting snapshotter transaction")
b.snapshot, err = b.snapshotter.StartTransaction()
if err != nil {
b.cfg.Logger.Errorf("failed to start snapshotter transaction")
return elementalError.NewFromError(err, elementalError.SnapshotterStart)
}
cleanup.PushErrorOnly(func() error { return b.snapshotter.CloseTransactionOnError(b.snapshot) })

system := b.spec.System
if b.spec.RecoverySystem.Source.String() == b.spec.System.String() {
// Reuse already deployed root-tree from recovery image
system = v1.NewFileSrc(b.spec.RecoverySystem.File)
b.spec.System.SetDigest(b.spec.RecoverySystem.Source.GetDigest())
}

// Deploy system image
err = elemental.DumpSource(b.cfg.Config, b.snapshot.WorkDir, system)
if err != nil {
b.cfg.Logger.Errorf("failed deploying source: %s", system.String())
return elementalError.NewFromError(err, elementalError.DumpSource)
}

// Closing snapshotter transaction
b.cfg.Logger.Info("Closing snapshotter transaction")
err = b.snapshotter.CloseTransaction(b.snapshot)
if err != nil {
b.cfg.Logger.Errorf("failed closing snapshot transaction: %v", err)
return err
}
}

// Add state.yaml file on state and recovery partitions
err = b.createBuildDiskStateYaml(
b.roots[constants.StatePartName],
b.roots[constants.RecoveryPartName],
)
if err != nil {
return elementalError.NewFromError(err, elementalError.CreateFile)
}

// Creates RAW disk image
Expand Down Expand Up @@ -378,18 +328,139 @@ func (b *BuildDiskAction) CreateRAWDisk(rawImg string) error {
// CreatePartitionImage creates partition image files and returns a slice of the created images
func (b *BuildDiskAction) CreatePartitionImages() ([]*v1.Image, error) {
var err error
var img *v1.Image
var img, stateImg *v1.Image
var images []*v1.Image
var excludes v1.PartitionList

excludes = append(excludes, b.spec.Partitions.EFI)
if b.spec.Expandable {
excludes = append(excludes, b.spec.Partitions.State, b.spec.Partitions.Persistent)
// Create state partition first to compute snapshot metadata if any
if !b.spec.Expandable {
b.cfg.Logger.Infof("Creating State partition image")
stateImg, err = b.createStatePartitionImage()
if err != nil {
b.cfg.Logger.Errorf("failed creating State partition img: %s", err.Error())
return nil, err
}
}

// Add state.yaml file on recovery partition including snapshot metadata if any
err = b.createBuildDiskStateYaml("", b.roots[b.spec.Partitions.Recovery.Name])
if err != nil {
b.cfg.Logger.Errorf("failed creating state file: %v", err)
return nil, elementalError.NewFromError(err, elementalError.CreateFile)
}

b.cfg.Logger.Infof("Creating EFI partition image")
img = b.spec.Partitions.EFI.ToImage()
err = elemental.CreateFileSystemImage(b.cfg.Config, img, "", false)
img, err = b.createEFIPartitionImage()
if err != nil {
b.cfg.Logger.Errorf("failed creating EFI img: %s", err.Error())
return nil, err
}
images = append(images, img)

for _, part := range []*v1.Partition{b.spec.Partitions.OEM, b.spec.Partitions.Recovery} {
b.cfg.Logger.Infof("Creating %s partition image", part.Name)
img = part.ToImage()
err = elemental.CreateImageFromTree(
b.cfg.Config, img, b.roots[part.Name], b.spec.Expandable,
func() error { return b.cfg.Fs.RemoveAll(b.roots[part.Name]) },
)
if err != nil {
b.cfg.Logger.Errorf("failed creating %s partition image: %s", part.Name, err.Error())
return nil, err
}
images = append(images, img)
}

if !b.spec.Expandable {
images = append(images, stateImg)

b.cfg.Logger.Infof("Creating Persistent partition image")
part := b.spec.Partitions.Persistent
img = part.ToImage()
err = elemental.CreateImageFromTree(
b.cfg.Config, img, b.roots[part.Name], b.spec.Expandable,
func() error { return b.cfg.Fs.RemoveAll(b.roots[part.Name]) },
)
if err != nil {
b.cfg.Logger.Errorf("failed creating %s partition image: %s", part.Name, err.Error())
return nil, err
}
images = append(images, img)
}

return images, nil
}

// createStatePartitionImage creates the State partitions for the configured snapshotter
func (b *BuildDiskAction) createStatePartitionImage() (*v1.Image, error) {
stateImg := b.spec.Partitions.State.ToImage()

err := elemental.CreateFileSystemImage(b.cfg.Config, stateImg, "", false)
if err != nil {
b.cfg.Logger.Error("failed creating state filesystem image: %v", err)
return nil, err
}

err = elemental.MountFileSystemImage(b.cfg.Config, stateImg, "rw")
if err != nil {
b.cfg.Logger.Error("failed mounting state filesystem image: %v", err)
return nil, err
}
defer func() {
_ = elemental.UnmountFileSystemImage(b.cfg.Config, stateImg)
}()

// Run a snapshotter transaction for System source in state partition
err = b.snapshotter.InitSnapshotter(b.spec.Partitions.State, b.roots[constants.EfiPartName])
if err != nil {
b.cfg.Logger.Errorf("failed initializing snapshotter")
return nil, elementalError.NewFromError(err, elementalError.SnapshotterInit)
}
// Starting snapshotter transaction
b.cfg.Logger.Info("Starting snapshotter transaction")
b.snapshot, err = b.snapshotter.StartTransaction()
if err != nil {
b.cfg.Logger.Errorf("failed to start snapshotter transaction")
return nil, elementalError.NewFromError(err, elementalError.SnapshotterStart)
}

system := b.spec.System
if b.spec.RecoverySystem.Source.String() == b.spec.System.String() {
// Reuse already deployed root-tree from recovery image
system = v1.NewFileSrc(b.spec.RecoverySystem.File)
b.spec.System.SetDigest(b.spec.RecoverySystem.Source.GetDigest())
}

// Deploy system image
err = elemental.DumpSource(b.cfg.Config, b.snapshot.WorkDir, system)
if err != nil {
_ = b.snapshotter.CloseTransactionOnError(b.snapshot)
b.cfg.Logger.Errorf("failed deploying source: %s", system.String())
return nil, elementalError.NewFromError(err, elementalError.DumpSource)
}

// Closing snapshotter transaction
b.cfg.Logger.Info("Closing snapshotter transaction")
err = b.snapshotter.CloseTransaction(b.snapshot)
if err != nil {
_ = b.snapshotter.CloseTransactionOnError(b.snapshot)
b.cfg.Logger.Errorf("failed closing snapshot transaction: %v", err)
return nil, err
}

// Add state.yaml file on state partition
err = b.createBuildDiskStateYaml(stateImg.MountPoint, "")
if err != nil {
b.cfg.Logger.Errorf("failed creating state file: %v", err)
return stateImg, elementalError.NewFromError(err, elementalError.CreateFile)
}

return stateImg, nil
}

// createEFIPartitionImage creates the EFI partition image
func (b *BuildDiskAction) createEFIPartitionImage() (*v1.Image, error) {
img := b.spec.Partitions.EFI.ToImage()
err := elemental.CreateFileSystemImage(b.cfg.Config, img, "", false)
if err != nil {
b.cfg.Logger.Errorf("failed creating EFI image: %s", err.Error())
return nil, err
Expand All @@ -415,29 +486,7 @@ func (b *BuildDiskAction) CreatePartitionImages() ([]*v1.Image, error) {

return nil
})
if err != nil {
b.cfg.Logger.Errorf("failed copying files to EFI img: %s", err.Error())
return nil, err
}

images = append(images, img)

// Create all partitions after EFI
for _, part := range b.spec.Partitions.PartitionsByInstallOrder(v1.PartitionList{}, excludes...) {
b.cfg.Logger.Infof("Creating %s partition image", part.Name)
img = part.ToImage()
err = elemental.CreateImageFromTree(
b.cfg.Config, img, b.roots[part.Name], b.spec.Expandable,
func() error { return b.cfg.Fs.RemoveAll(b.roots[part.Name]) },
)
if err != nil {
b.cfg.Logger.Errorf("failed creating %s partition image: %s", part.Name, err.Error())
return nil, err
}
images = append(images, img)
}

return images, nil
return img, err
}

// CreateDiskImage creates the final image by truncating the image with the proper size and
Expand Down Expand Up @@ -662,6 +711,8 @@ func (b *BuildDiskAction) applySelinuxLabels(root string, unprivileged bool) err
}

func (b *BuildDiskAction) createBuildDiskStateYaml(stateRoot, recoveryRoot string) error {
var statePath, recoveryPath string

if b.spec.Partitions.Recovery == nil {
return fmt.Errorf("undefined recovery partition")
}
Expand Down Expand Up @@ -714,15 +765,15 @@ func (b *BuildDiskAction) createBuildDiskStateYaml(stateRoot, recoveryRoot strin
}
}

statePath := ""
if !b.spec.Expandable {
if stateRoot != "" {
statePath = filepath.Join(stateRoot, constants.InstallStateFile)
}

return b.cfg.WriteInstallState(
installState, statePath,
filepath.Join(recoveryRoot, constants.InstallStateFile),
)
if recoveryRoot != "" {
recoveryPath = filepath.Join(recoveryRoot, constants.InstallStateFile)
}

return b.cfg.WriteInstallState(installState, statePath, recoveryPath)
}

func (b *BuildDiskAction) SetExpandableCloudInitStage() error {
Expand Down
4 changes: 2 additions & 2 deletions pkg/action/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,13 @@ var _ = Describe("Build Actions", func() {

Expect(runner.MatchMilestones([][]string{
{"mksquashfs", "/tmp/test/build/recovery.img.root", "/tmp/test/build/recovery/recovery.img"},
{"mkfs.ext4", "-L", "COS_STATE"},
{"losetup", "--show", "-f", "/tmp/test/build/state.part"},
{"mkfs.vfat", "-n", "COS_GRUB"},
{"mkfs.ext4", "-L", "COS_OEM"},
{"losetup", "--show", "-f", "/tmp/test/build/oem.part"},
{"mkfs.ext4", "-L", "COS_RECOVERY"},
{"losetup", "--show", "-f", "/tmp/test/build/recovery.part"},
{"mkfs.ext4", "-L", "COS_STATE"},
{"losetup", "--show", "-f", "/tmp/test/build/state.part"},
{"mkfs.ext4", "-L", "COS_PERSISTENT"},
{"losetup", "--show", "-f", "/tmp/test/build/persistent.part"},
{"sgdisk", "-p", "/tmp/test/elemental.raw"},
Expand Down
2 changes: 1 addition & 1 deletion pkg/action/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func (i InstallAction) Run() (err error) {
return elemental.UnmountPartitions(i.cfg.Config, i.spec.Partitions.PartitionsByMountPoint(true))
})

err = i.snapshotter.InitSnapshotter(i.spec.Partitions.State.MountPoint)
err = i.snapshotter.InitSnapshotter(i.spec.Partitions.State, i.spec.Partitions.EFI.MountPoint)
if err != nil {
i.cfg.Logger.Errorf("failed initializing snapshotter")
return elementalError.NewFromError(err, elementalError.SnapshotterInit)
Expand Down
2 changes: 1 addition & 1 deletion pkg/action/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func (r ResetAction) Run() (err error) {
})

// Init snapshotter
err = r.snapshotter.InitSnapshotter(r.spec.Partitions.State.MountPoint)
err = r.snapshotter.InitSnapshotter(r.spec.Partitions.State, r.spec.Partitions.EFI.MountPoint)
if err != nil {
return elementalError.NewFromError(err, elementalError.SnapshotterInit)
}
Expand Down
11 changes: 2 additions & 9 deletions pkg/action/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,8 @@ func (u *UpgradeAction) upgradeInstallStateYaml() error {
}
}

// Hack to ensure we are not using / or /.snapshots mountpoints. Btrfs based deployments
// mount state partition into multiple locations
statePath := filepath.Join(u.spec.Partitions.State.MountPoint, constants.InstallStateFile)
if u.spec.Partitions.State.MountPoint == "/" || u.spec.Partitions.State.MountPoint == "/.snapshots" {
statePath = filepath.Join(constants.RunningStateDir, constants.InstallStateFile)
}

return u.cfg.WriteInstallState(
u.spec.State, statePath,
u.spec.State, filepath.Join(u.spec.Partitions.State.MountPoint, constants.InstallStateFile),
filepath.Join(u.spec.Partitions.Recovery.MountPoint, constants.InstallStateFile),
)
}
Expand Down Expand Up @@ -256,7 +249,7 @@ func (u *UpgradeAction) Run() (err error) {
}

// Init snapshotter
err = u.snapshotter.InitSnapshotter(u.spec.Partitions.State.MountPoint)
err = u.snapshotter.InitSnapshotter(u.spec.Partitions.State, u.spec.Partitions.EFI.MountPoint)
if err != nil {
u.cfg.Logger.Errorf("failed initializing snapshotter")
return elementalError.NewFromError(err, elementalError.SnapshotterInit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ if [ "${snapshotter}" == "btrfs" ]; then
echo "[Unit]"
echo "Before=initrd-root-fs.target"
echo "DefaultDependencies=no"
echo "RequiresMountsFor=/sysroot"
echo "PartOf=initrd.target"
echo "[Mount]"
echo "Where=${root_part_mnt}"
Expand Down
Loading

0 comments on commit e04b8c2

Please sign in to comment.